10.27--11.3 一周刷题记录
Posted sjsjsj-minus-si
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了10.27--11.3 一周刷题记录相关的知识,希望对你有一定的参考价值。
exgcd
贝祖定理推广
贝祖定理可以推广到n个,n>=2
ax+by+cz+...=k
if(k%gcd(a,b,c,...)==0)
该式子有整数解
else
没有整数解
Forsaken喜欢数论
- 因为空间限制,直接省略掉sum数组
- 记得sum 开long long!
int n;
const int N=3e7+10;
int v[N],p[N],tot_p;
long long sum;
inline void shai(){
rep(i,2,n){
if(v[i]==0){
v[i]=i;
p[++tot_p]=i;
}
rep(j,1,tot_p){
if(v[i]<p[j] || p[j]>N/i)break;
v[i*p[j]]=p[j];
}
sum+=(long long)v[i];
}
}
int main(){
#ifdef WIN32
freopen("a.txt","r",stdin);
#endif
rd(n);
shai();
// rep(i,1,n)printf("%d ",sum[i]);
printf("%lld
",sum);
// printf("%.2lf",(double)sizeof(v)/(1<<20));
return 0;
}
乘法逆元2
刚刚学到的众所周知的科技可以 O(n+log?p)离线求出任意 n个数在模 p意义下的逆元。
首先计算 n个数的前缀积,记为 (s_i),然后使用快速幂或扩展欧几里得法计算 (s_n) 的逆元,记为 (sv_n)。
因为(sv_n) 是 n 个数的积的逆元,所以当我们把它乘上 (a_n) 时,就会和 (a_n) 的逆元抵消,于是就得到了 a1 到(a_{n-1})的积逆元,记为 (sv_{n-1})。
同理我们可以依次计算出所有的 (sv_i)。
于是 (a_i^{-1}) 就可以用 (s_{i-1})×(sv_i) 求得。
int n,m;
const int N=5e6+10,mod=1e9+7;
int inv[N],sum[N],a[N];
inline int ksm(int a,int k,int mod){
int res=1;
for(;k;k>>=1){
if(k&1)res=(res*a)%mod;
a=(a*a)%mod;
}
return res%mod;
}
#undef int
int main(){
#define int long long
#ifdef WIN32
freopen("c.txt","r",stdin);
#endif
rd(n);
rep(i,1,n)rd(a[i]);
sum[0]=1;
rep(i,1,n)sum[i]=sum[i-1]*a[i]%mod;
inv[n]=ksm(sum[n],mod-2,mod);
dwn(i,n-1,1)
inv[i]=inv[i+1]*a[i]%mod;
int ans=0;
rep(i,1,n)
ans=(ans*998244353%mod+inv[i]%mod*sum[i-1]%mod)%mod;
printf("%lld
",ans);
return 0;
}
inline int exgcd(int a,int b,int &x,int &y){
if(b==0){x=1,y=0;return a;}
int d=exgcd(b,a%b,x,y);
int z=x;
x=y;
y=z-(a/b)*y;
return d;
}
int A1=a[1],B1=b[1];
rep(i,2,n){
int A2=a[i],B2=b[i];
int deltaA=((A2-A1)%B2+B2)%B2;
int d=exgcd(B1,B2,x,y);
x=ksc(x,deltaA/d,B2);
A1+=B1*x;
B1*=B2/d;
(A1+=B1)%=B1;
}
?
线性基
定义
设数集T的值域范围为[1,(2^n)?1]
T的线性基是T的一个子集A={a1,a2,a3,...,an}
A中元素互相xor所形成的异或集合,等价于原数集T的元素互相xor形成的异或集合。
可以理解为将原数集进行了压缩。
性质
1.设线性基的异或集合中不存在0。
2.线性基的异或集合中每个元素的异或方案唯一,其实这个跟性质1是等价的。
3.线性基二进制最高位互不相同。
4.如果线性基是满的,它的异或集合为[1,(2^n)?1]
5.线性基中元素互相异或,异或集合不变。
查询k小值
根据性质3。
我们要将线性基改造成每一位相互独立。
具体操作就是如果j<i,ai的第j位是1,就将ai异或上aj。
经过一系列操作之后,对于二进制的某一位i。只有ai的这一位是1,其他都是0。
所以查询的时候将k二进制拆分,对于1的位,就异或上对应的线性基。
最终得出的答案就是k小值。
const int I=50+5;
int a[50+5],tmp[50+5];
int n,x;
bool flag;//判断能不能表示出0
inline void insert(int x){
dwn(i,I,0)
if(x&(1ll<<i))
if(!a[i]){a[i]=x;return;}
else x^=a[i];
flag=true;
}
inline bool check(int x){
dwn(i,I,0)
if(x&(1ll<<i))
if(!a[i])return false;
else x^=a[i];
return true;
}
inline int qmax(){
int res=0;
dwn(i,I,0)
res=max(res,res^a[i]);
return res;
}
inline int qmin(){
if(flag)return 0;
rep(i,0,I)
if(a[i])return a[i];
}
inline int query(int k){
int cnt=0;
k-=flag;
if(!k)return 0;
rep(i,0,I){
dwn(j,i-1,0)
if(a[i]&(1ll<<j))a[i]^=a[j];
if(a[i])tmp[cnt++]=a[i];
}
if(k>=(1ll<<cnt))return -1;
int res=0;
rep(i,0,cnt-1)
if(k&(1<<i))res^=tmp[i];
return res;
}
#undef int
int main(){
#define int long long
rd(n);
rep(i,1,n){
rd(x);insert(x);
}
printf("%lld
",qmax());
return 0;
}
P3292 [SCOI2016]幸运数字
可以说是树上路径最大异或和模板题。
考虑到两个序列合并的线性基就是它们的线性基的合并,这给我们解决问题带来了新的思路,通过这个思路可以解决区间线性基的问题。这里对于一棵树,我们对它进行树链剖分,用线段树维护一段区间的线性基,查询时合并这些线性基。
经过合并后可以得到树上路径线性基,求它的最大值即可。不过这种方法复杂度很高,树剖和线段树都是(logn),线性基合并是(log^2n),达到(O(nlog^4n))的复杂度。
好像还可以用点分治,复杂度比较低,不过我不会。
仪仗队
/*
reference:
https://www.luogu.org/blog/JustinRochester/solution-p2158
translation:
solution:
trigger:
gcd(a,b)==1 才能被看见,记录gcd(a,b)==1 的点对个数,直接用gcd会t,枚举每个横坐标,
线性求纵坐标与之互素的欧拉函数,累加答案即可。
note:
*欧拉筛、线性筛素数、分治
date:
2019.08.10
*/
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,a,b) for(ll i=a;i<=b;++i)
#define dwn(i,a,b) for(ll i=a;i>=b;--i)
template <typename T> inline void rd(T &x){x=0;char c=getchar();int f=0;while(!isdigit(c)){f|=c=='-';c=getchar();}while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}x=f?-x:x;}
#define mem(a,b) memset(a,b,sizeof(a))
#define N 40010
bool ip[N];
int n;
int p[N],phi[N],sum[N];
void Euler(){
mem(ip,1);
ip[1]=0;
phi[1]=1;
sum[1]=1;
int tot=0;
rep(i,2,N){
if(ip[i]){
p[++tot]=i;
phi[i]=i-1;
}
for(int j=1;j<=tot && i*p[j]<=N;++j){
ip[i*p[j]]=0;
if(!(i%p[j])){
phi[i*p[j]]=phi[i]*p[j];
break;
}
phi[i*p[j]]=phi[i]*phi[p[j]];
}
sum[i]=sum[i-1]+phi[i];
}
}
int main(){
Euler();
rd(n);
if(n==1)printf("0
");
else printf("%d
",2*sum[n-1]+1);//加的“1”是(2,2)
return 0;
}
两遍dfs求树的直径
这种方法可以找到路径。
- 对于两条相交直径,他们不相交的部分一定对称。
- 两棵树用一条边合并,新树的直径两端一定是原本两棵树直径四个端点中的两个。
- 证明:
- (1)直径不经过新边 这个时候显然是原本两条直径中的一条。否则就不满足直径的定义了。
- (2)直径经过新边。 新边两端分属两棵树,那么这条直径的新边的两端部分肯定是从这两个点出发在各自树中的最长路径。根据性质3,端点还是四个端点的其中之二。
- 证明:
inline void work(){
dfs(1,0);//0号节点是虚点,编号要从1开始编
int x=1;
rep(i,2,n)
if(deep[i]>deep[x])
x=i;
dfs(x,0);
int y=1;
rep(i,2,n)
if(deep[i]>deep[y])
y=i;
printf("%d %d",x,y);//直径的俩端点
printf("%d",deep[y]);//直径
}
树的中心
类似于圆的圆心,树的中心是该点到其他点的最远距离最小
- 一定在树的直径上,且到直径两端的距离差不超过1
- 最多俩个树的中心,在直径的中点
首先通过上一段代码求出树的直径,再写一段lca(用树剖好 了)。
int work(){
int deep_lca=deep[lca(u,v)];
int len=deep[x]+deep[y]-2*deep_lca;
if(deep[x]-deep_lca>len/2){
rep(i,1,len/2)u=fa[u];
return u;
}
else{
rep(i,1,len-len/2)v=fa[v];
return v;
}
}
通过求维护lca的最长链和次长链来求树的直径
int len=0;
inline int dfs(int u,int fa){
int max1=0,max2=0;
ee(i,u){
int v=e[i].v;
int tmp=dfs(v,u);
if(tmp>max1)max2=max1,max1=tmp;
else if(tmp>max2)max2=tmp;
}
len=max(len,max1+max2);
return max1;
}
树的重心
? 首先认识什么是重心。
? 对于一棵无根树,我们需要选一个点作为根节点将其转化为有根树。如果以某一个点为根可以使得其最大的子树结点数最小,那么这个点称为树的重心。重心可以有多个。
? 以重心为根可以得到尽可能平衡的有根树,下面是重心的其它性质:
树中所有点到某个点的距离和中,到重心的距离和是最小的。有两个重心时,它们的距离和一样。
把两个树通过一条边相连得到一个新的树,新的树的重心在连接原来两个树的重心的路径上。
把一个树添加或删除一个叶子,那么它的重心最多只移动一条边的距离。
846. 树的重心 表示重心的所有的子树中最大的子树的结点数目。
int n,ans=INT_MAX;
int siz[N],son[N];
inline void dfs(int u,int fa){
siz[u]=1;
ee(i,u){
int v=e[i].v;
if(v==fa)continue;
dfs(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])
son[u]=v;
}
int max_size=max(siz[son[u]],n-siz[u]);
ans=min(ans,max_size);//求重心具体是谁:如果ans被更新了就记录一下u
}
int main(){
#ifdef WIN32
freopen("b.txt","r",stdin);
#endif
rd(n);
rep(i,1,n-1){
int u,v;rd(u),rd(v);
adde(v,u),adde(u,v);
}
dfs(1,0);
printf("%d",ans);
return 0;
}
P2052 [NOI2011]道路修建
考虑每条边的贡献:e[i].w*abs(siz[v]-(n-siz[v]));
- long long,你要遭多少回
- 虽然是树,但人家是双向的无根树啊e要开N<<1
- 用扩展欧几里得算法求解逆元
? 当a与p互质时,可以用扩展欧几里得算法求出一个特解x、y使得ax+bp=1ax+bp=1,也就是ax≡1(mod p)ax≡1(mod p),这样x就是逆元。本方法只需要a与p互质(这也是逆元存在的条件),缺点是求出的x可能为负数。
P1229 遍历问题
if(s1[i]==s2[j] && s1[i+1]==s2[j-1])
- s1[i]==s2[j]:找到了此子树的根
- s1[i+1]是此子树的左儿子,s2[j+1]是此子树的右儿子,如果他俩相等的话说明此子树只有一个儿子,那么这个儿子就就可能是左儿子和右儿子,根据分步相乘原理,中序遍历的方案就要*=2;
int ans;
char s1[233],s2[233];
int main(){
scanf("%s",s1+1);
scanf("%s",s2+1);
int len=strlen(s1+1);
rep(i,1,len-1)
rep(j,2,len)
if(s1[i]==s2[j] && s1[i+1]==s2[j-1])
ans++;
printf("%d",1<<ans);
return 0;
}
咕咕咕列表
- [CH Round #30 摆花矩阵乘法
- [CH Round #72 奇数码问题逆序对 观察]
- CODEVS 2070 爱情之路(二维SPFA)
- POJ1386Play on Words有向图欧拉路
POJ3635 Full Tank?(分层最短路)
n个城市之间有m条双向路。每条路要耗费一定的油量。
每个城市的油价是固定并且已经给出的。有q个询问,表示从城市s走到e,油箱的容量为c,求最便宜的方案。
SOL:
dis(i,j)表示走到城市i,剩余油量为j的最小花费。然后用优先队列更新,优先更新花费小的状态。
- 走到一座城市
- 加一升油(可以一直加到c),贡献p[u]
- 走向下一个城市(在油量充足的情况下),贡献为0。
==【信压】使用分层最短路的问题:一个状态可以转化为多种状态并且其中一种或几种贡献为0,一种或几种有贡献,并求贡献的属性(Min/Max...)==
一看到dis[i] [j]这样的状态表示,马上想到分层图最短路,有点类似DP的思想
按油量分层,dis[i] [j]表示到节点i还有j个油的最小花费(不是最短路)
两种决策,加一个油或者直接走
关于运算符的重载:
struct point{
int x;
int y;
int times;
friend bool operator < (point a, point b)
{
return a.times > b.times; //重载小于号使得小的先出队列
}
};
在此处定义一个优先队列priority_queue
如果要按照以times进行从小到大排列,操作如上。
进行重载<操作符。
意思是如果a.times > b.times成立,那么结构体point a < point b成立。由于优先队列是按照从大到小排列,所以结构体b会排列到a之前,然而b.times是最小的,所以实现了按照times的从小到大排序,其实用一句话说就是要想b更大那么b.times在结构体中比较时需要进行运算符的重载(重载<),在不需要结构体时:
priority_queue<int,vector<int>,less<int>>s;//定义优先级队列s,less表示按照递减(从大到小)的顺序插入元素
priority_queue<int,vector<int>,greater<int>>s;//定义优先级队列s,greater表示按照递增(从小到大)的顺序插入元素
const int N=1e3+10,M=1e4+10,V=1e2+10;
int dis[N][V];//dis[i][j]到i号点还剩j升油的最小花费
bool vis[N][V];//判断此点有没有更新过
int n,m,q;
int p[N];
struct edge{
int v,w,next;
}e[M<<1];
int head[N],edge_num;
inline void adde(int u,int v,int w){
e[++edge_num].v=v;
e[edge_num].w=w;
e[edge_num].next=head[u];
head[u]=edge_num;
}
struct node{
int id,cost,fuel;
bool operator <(const node &rhs)const{
return cost>rhs.cost;
}
};
inline void dij(int c,int s,int t){
mem(dis,0x3f);
mem(vis,0);
priority_queue<node>q;
dis[s][0]=0;
q.push((node){s,0,0});
while(!q.empty()){
node st=q.top();q.pop();
int u=st.id,fuel=st.fuel,cost=st.cost;
if(vis[u][fuel])continue;
vis[u][fuel]=1;
if(u==t){
printf("%d
",cost);
return ;
}
//加一升油
if(fuel+1<=c && dis[u][fuel+1]>dis[u][fuel]+p[u]){
dis[u][fuel+1]=dis[u][fuel]+p[u];
q.push((node){u,dis[u][fuel+1],fuel+1});
}
//不加油,直接开到下一条边。
ee(i,u){
int v=e[i].v,w=e[i].w;
if(fuel>=w && dis[v][fuel-w]>cost){
dis[v][fuel-w]=cost;
q.push((node){v,cost,fuel-w});
}
}
}
printf("impossible
");
}
int main(){
#ifdef WIN32
freopen("b.txt","r",stdin);
#endif
rd(n),rd(m);
rep(i,0,n-1)rd(p[i]);//注意点是从0开始的!阿伟debug了好久
rep(i,1,m){
int u,v,w;rd(u),rd(v),rd(w);
adde(u,v,w),adde(v,u,w);
}
rd(q);
while(q--){
int c,s,t;rd(c),rd(s),rd(t);
dij(c,s,t);
}
return 0;
}
893. 集合-Nim游戏
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define dwn(i,a,b) for(int i=(a);i>=(b);--i)
template <typename T>inline void rd(T &x){
x=0;char c=getchar();int flag=0;
while(!isdigit(c)){flag|=c=='-';c=getchar();}
while(isdigit(c)){x=x*10+(c^48);c=getchar();}
x=flag?-x:x;
}
#define mem(a,b) memset(a,b,sizeof(a))
#define ee(i,u) for(int i=head[u];i;i=e[i].next)
/******************************************** Header Template **********************************************/
int n,m,ans;
const int N=110,M=1e4+10;
int s[N],sg[M];
inline int SG(int x){
if(sg[x]!=-1)return sg[x];
unordered_set<int>S;
rep(i,1,m){
if(x>=s[i])
S.insert(SG(x-s[i]));
}
for(int i=0; ;++i)
if(!S.count(i))
return sg[x]=i;
}
int main(){
rd(m);
rep(i,1,m)rd(s[i]);
rd(n);
mem(sg,-1);
rep(i,1,n){
int x;rd(x);
ans^=SG(x);
}
if(ans==0)printf("No");
else printf("Yes");
return 0;
}
884. 高斯消元解异或线性方程组
const int N=110;
const double eps=1e-6;
int n;
int a[N][N];
int main(){
rd(n);
rep(i,1,n)
rep(j,1,n+1)
rd(a[i][j]);
rep(i,1,n){
int max=i;
rep(j,i+1,n)
if(a[j][i])
max=j;
rep(k,1,n+1)
swap(a[max][k],a[i][k]);
rep(j,1,n)if(i!=j){
if(a[j][i]){
rep(k,i,n+1)
a[j][k]^=a[i][k];
}
}
}
rep(i,1,n){
if(a[i][i]==0){
if(a[i][n+1]){
puts("No solution");
exit(0);
}
else{
puts("Multiple sets of solutions");
exit(0);
}
}
}/*
rep(i,1,n){
rep(j,1,n+1)
printf("%d ",a[i][j]);
puts("");
}*/
rep(i,1,n)
printf("%d
",a[i][n+1]);
return 0;
}
884. 高斯消元解异或线性方程组
int n;
int a[110][110];
int main(){
rd(n);
rep(i,1,n)
rep(j,1,n+1)
rd(a[i][j]);
rep(i,1,n){
int max=i;
rep(j,i+1,n)
if(a[j][i]==1)
max=j;
rep(k,1,n+1)
swap(a[max][k],a[i][k]);
rep(j,1,n)if(i!=j){
if(a[j][i])//当前元对a[i][i]有影响
rep(k,i,n+1)
a[j][k]^=a[i][k];//对j的这组方程做逆运算,^的逆运算就是^
}
}
rep(i,1,n){
if(a[i][i]==0){
if(a[i][n+1]==1){
puts("No solution");
exit(0);
}
else {
puts("Multiple sets of solutions");
exit(0);
}
}
}
rep(i,1,n)
printf("%d
",a[i][n+1]);//不用写/a[i][i]。因为a[i][i]==0的时候上面已经判断过了
return 0;
}
AcWing 894. 拆分-Nim游戏
const int N=110;
int sg[N],a[N];
int n;
inline int SG(int x){
if(sg[x]!=-1)return sg[x];
unordered_set<int>S;//开一个哈希表
rep(i,0,x-1)
rep(j,0,i)
S.insert(SG(i)^SG(j));//当前可能变成两个局面,这俩局面先异或一下
for(int i=0; ;++i)
if(!S.count(i))
return sg[x]=i;
}
int main(){
rd(n);
int ans=0;
mem(sg,-1);
rep(i,1,n){
int x;rd(x);
ans^=SG(x);
}
if(ans)puts("Yes");
else puts("No");
}
P3150 pb的游戏
必胜态:2
必败态:1
先手要确保自己能够率先拿到2,那就必须确保后手能够分出偶数来。
- 后手手里拿的是偶数:哼,我不留情面,给你分俩奇数,看你怎么选
- 后手手里拿的是奇数:糟糕,无论怎么分都能分出一奇数一偶数,先手就能够达到偶数状态。
至此,我们已经分析出来了,先手如果能保证后手每次都拿到奇数(说明先手手里的数是偶数,他就会拆成奇数+奇数)就会胜利,==先手创造一个必败态给后手,而后手没有办法创造一个必败态给先==手(先手一定会选划分出来的偶数态)
==这个模型就是在攻占偶数态。==
答案就是
if(n&1)后手胜
else 先手胜
866. 试除法判定质数
单个数时间复杂度O((sqrt{x}))
bool is_prime(int x){
if(x==1)return 0;
if(x==2 || x==3)return 1;
if(x%6!=1 && x%6!=5)return 0;
for(int i=5;i*i<=x;i+=6){
if(x%i==0 || x%(i+2)==0)return 0;
}
return 1;
}
867. 分解质因数
一开始我还在算a<=2e9,一共有约1e8个质数,p[] 和 c[]的数组怎么开啊……难道要用unordered_map?哎呀,其实边分解就可以边输出啦。笨笨。
inline void divide(int x){
for(int i=2;i*i<=x;++i){
if(x%i==0){
int cnt=0;
while(x%i==0){
x/=i;
cnt++;
}
printf("%d %d
",i,cnt);
}
}
if(x>1)printf("%d %d
",x,1);
}
给定一个正整数n,请你求出1~n中质数的个数。
这不是欧拉函数啊,姐姐,phi[i]是1~i-1的所有与i互质的数,而不是质数,姐姐。
直接输出tot_p就得了
给定一个正整数n,求1~n中每个数的欧拉函数之和。
==不开long long见祖宗==
n<=1e6
给定n个正整数ai,对于每个整数ai,请你按照从小到大的顺序输出它的所有约数。
对于要输出1和本身的情况,一定不能写while(x%i==0)x/=i;因为i ==1的时候会死循环,因为为了节省时间复杂度,i和x/i是一起求的,所以还是要sort一遍再输出。
int n;
const int N=1e6+10;
int p[N];
inline void divide(int x){
mem(p,0);
int cnt=0;
for(int i=1;i*i<=x;++i){
if(x%i==0){
p[++cnt]=i;
if(i*i!=x)
p[++cnt]=x/i;
}
}
sort(p+1,p+cnt+1);
rep(i,1,cnt)printf("%d ",p[i]);
}
int main(){
rd(n);
rep(i,1,n){
int x;rd(x);
divide(x);
puts("");
}
return 0;
}
给定n个正整数ai,请你输出这些数的乘积的约数个数,答案对1e+7取模。
int n;
const int mod=1e9+7;
map<int,int>p;
inline void divide(int x){
for(int i=2;i*i<=x;++i){
while(x%i==0){
p[i]++;
x/=i;
}
}
if(x>1)p[x]++;
}
#undef int
int main(){
#define int long long
rd(n);
rep(i,1,n){
int x;rd(x);
divide(x);
}
int ans=1;
for(map<int,int>::iterator it=p.begin();it!=p.end();++it){
(ans*=(it->second+1))%=mod;
}
printf("%lld
",ans);
return 0;
}
给定n个正整数ai,请你输出这些数的乘积的约数之和,答案对1e9+7取模。
秦久韶推公式的时候自己手推几组小样例。
int n;
const int mod=1e9+7;
map<int,int>p;
inline void divide(int x){
for(int i=2;i*i<=x;++i){
while(x%i==0){
p[i]++;
x/=i;
}
}
if(x>1)p[x]++;
}
#undef int
int main(){
#define int long long
rd(n);
rep(i,1,n){
int x;rd(x);
divide(x);
}
int res=1;
for(map<int,int>::iterator it=p.begin();it!=p.end();++it){
int pp=it->first;
int c=it->second;
int t=1;//存当前质数pp的贡献。
while(c--){
t=(t*pp+1)%mod;
}
res=(res*t)%mod;
}
printf("%lld
",res);
return 0;
}
快速幂求逆元
a[i] 在mod p意义下没有逆元的情况:
- p为质数,a%p==0
- p不为质数,gcd(a,p)!=1
AcWing 878. 线性同余方程
Q:请问最后的答案为什么模了m?
A:题目中要求“输出答案必须在int范围之内”,对m取模后可以保证答案在int内。
int n,x,y;
inline int exgcd(int a,int b,int &x,int &y){
if(b==0){x=1,y=0;return a;}
int d=exgcd(b,a%b,y,x);
y-=(a/b)*x;
return d;
}
#undef int
int main(){
#define int long long
rd(n);
rep(i,1,n){
int a,b,m;rd(a),rd(b),rd(m);
int d=exgcd(a,m,x,y);
if(b%d)printf("impossible
");
else
printf("%lld
",x*(b/d)%m);//模到m范围内
}
return 0;
}
【excrt】204. 表达整数的奇怪方式
==无解的情况:exgcd的时候deltaA/d不为0==
const int N=30+5;
int n,x,y;
int a[N],b[N];
inline int exgcd(int a,int b,int &x,int &y){
if(b==0){x=1,y=0;return a;}
int d=exgcd(b,a%b,x,y);
int z=x;
x=y;
y=z-(a/b)*y;
return d;
}
inline int ksc(int a,int k,int mod){
int res=0;
for(;k;k>>=1){
if(k&1)res=(res+a)%mod;
a=(a+a)%mod;
}
return res;
}
#undef int
int main(){
#define int long long
rd(n);
rep(i,1,n){
rd(b[i]),rd(a[i]);
}
int A1=a[1],B1=b[1];
rep(i,2,n){
int A2=a[i],B2=b[i];
int deltaA=((A2-A1)%B2+B2)%B2;
int d=exgcd(B1,B2,x,y);
x=ksc(x,deltaA/d,B2);
if(deltaA%d){
printf("-1");
exit(0);
}
A1+=B1*x;
B1*=(B2/d);
(A1+=B1)%=B1;
}
printf("%lld",A1);
return 0;
}
求组合数1
1≤n≤100001≤n≤10000,
1≤b≤a≤2000
SOL:递推求解
int n;
const int N=2e3,mod=1e9+7;
int c[N+10][N+10];
int main(){
rd(n);
rep(i,1,N)c[i][0]=c[i][i]=1;
rep(i,1,N)
rep(j,1,i-1)//c[i-1][j]的时候要j<=i-1
c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
rep(i,1,n){
int a,b;rd(a),rd(b);
printf("%d
",c[a][b]);
}
return 0;
}
## 求组合数 II
1≤n≤10000
1≤b≤a≤1e5
SOL:
公式法。
预处理出fac[]和invfac[]
(O(n+log(N)))线性求阶乘的逆元
int n;
const int N=1e5,mod=1e9+7;
int fac[N+10];
int invfac[N+10];
inline int ksm(int a,int k,int mod){
int res=1;
for(;k;k>>=1){
if(k&1)res=res*a%mod;
a=a*a%mod;
}
return res;
}
#undef int
int main(){
#define int long long
rd(n);
fac[0]=invfac[0]=1;
rep(i,1,N){
fac[i]=(fac[i-1]*i)%mod;
}
invfac[N]=ksm(fac[N],mod-2,mod);
dwn(i,N-1,1)
invfac[i]=invfac[i+1]*(i+1)%mod;
rep(i,1,n){
int a,b;rd(a),rd(b);
printf("%lld
",fac[a]*invfac[a-b]%mod*invfac[b]%mod);
}
return 0;
}
满足条件的01序列
1≤n≤10^5
Cat(2n,n)=C(2n,n)-C(2n,n-1)=C(2n,n)/(n+1)
const int N=2e5;
const int mod=1e9+7;
int fac[N+10],invfac[N+10];
int n;
inline int ksm(int a,int k,int mod){
int res=1;
for(;k;k>>=1){
if(k&1)res=res*a%mod;
a=a*a%mod;
}
return res;
}
#undef int
int main(){
#define int long long
rd(n);
fac[0]=invfac[0]=1;
rep(i,1,2*n)
fac[i]=fac[i-1]*i%mod;
invfac[2*n]=ksm(fac[2*n],mod-2,mod);
dwn(i,2*n-1,1)
invfac[i]=invfac[i+1]*(i+1)%mod;
printf("%lld",fac[2*n]*invfac[n]%mod*invfac[n]%mod*ksm(n+1,mod-2,mod)%mod);
return 0;
}
【容斥原理】能被整除的数
用二进制数枚举所有可能的组合方案数
由于
$ sumlimits_{i=0}^nC_n^i=2^n$
所以
(sumlimits_{i=1}^n C_n^i = 2^n -1)
const int N=20+5;
int n,m;
int p[N];
#undef int
int main(){
#define int long long
rd(n),rd(m);
rep(i,0,m-1)rd(p[i]);
int res=0;
for(int i=1;i< 1<<m;++i){
int t=1,cnt=0;
rep(j,0,m-1){
if(i>>j&1){
cnt++;
t*=p[j];
if(t>n){
t=-1;
break;
}
}
}
if(t!=-1){
if(cnt&1)res+=n/t;
else res-=n/t;
}
}
printf("%lld",res);
return 0;
}
组合恒等式
(k imes C_n^k = n imes C_{n-1}^{k-1})
(C_k^n imes C_m^k = C_m^n imes C_{m-n}^{m-k}) ((m-k<m-n))
(sum limits_{k=0}^n(-1)^k imes C_n^k = 0)
(sum limits_{k=1}^m C_{n+k}^k = C_{n+m+1}^{m})
支持插入查询的trie
const int N=1e5+10,M=3e6+10;
int n;
int a[N];
int tree[M][2];
int idx;
inline void insert(int x){
int p=0;
dwn(i,30,0){
int &son=tree[p][x>>i&1];
if(!son)son=++idx;
p=son;
}
}
inline int query(int x){
int res=0,p=0;
dwn(i,30,0){
int now=x>>i&1;
if(tree[p][!now]){
res+=1<<i;
p=tree[p][!now];
}
else
p=tree[p][now];
}
return res;
}
int main(){
rd(n);
rep(i,1,n){
rd(a[i]);
insert(a[i]);
}
int ans;
rep(i,1,n){
ans=max(ans,query(a[i]));
}
printf("%d
",ans);
return 0;
}
const int N=1e5+10;
int tree[N][26];
char str[N];
int cnt[N];
int idx;
inline void insert(char s[]){
int p=0;
for(int i=0;s[i];++i){
int id=s[i]-'a';
int &son=tree[p][id];
if(!son)son=++idx;
p=son;
}
cnt[p]++;
}
inline int query(char s[]){
int p=0;
for(int i=0;s[i];++i){
int id=s[i]-'a';
if(!tree[p][id])return 0;
p=tree[p][id];
}
return cnt[p];
}
int main(){
int q;rd(q);
while(q--){
char op;cin>>op;
scanf("%s",str);
if(op=='I')insert(str);
else printf("%d
",query(str));
}
return 0;
}
const int N=1e6+10;
int len1,len2;
char t[N];//模式串
char p[N];//模板串
int nxt[N];
inline void getnext(){
int i=0,j=-1;
nxt[0]=-1;
while(i<len2){
if(j==-1 || p[i]==p[j])
nxt[++i]=++j;
else j=nxt[j];
}
}
inline void kmp(){
int i=0,j=0;
while(i<len1){
if(j==-1 || t[i]==p[j])
i++,j++;
else j=nxt[j];
if(j==len2){
printf("%d ",i-len2);
j=nxt[j];
}
}
}
int main(){
rd(len2);
scanf("%s",p);
rd(len1);
scanf("%s",t);
getnext();
kmp();
}
const int N=1e5+10;
int n,cnt;
struct qujian{
int l,r;
friend bool operator <(const qujian a,const qujian b){
return a.l<b.l;
}
}range[N];
int main(){
rd(n);
rep(i,1,n)
rd(range[i].l),rd(range[i].r);
sort(range+1,range+n+1);
int l=range[1].l,r=range[1].r;
rep(i,2,n){
if(r<range[i].l){
// printf("%d %d
",l,r);
cnt++;
l=range[i].l,r=range[i].r;
}
r=max(r,range[i].r);
}
cnt++;
printf("%d
",cnt);
// printf("%d %d
",l,r);
return 0;
}
(残品)选学霸
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define dwn(i,a,b) for(int i=(a);i>=(b);--i)
template <typename T>inline void rd(T &x){
x=0;char c=getchar();int flag=0;
while(!isdigit(c)){flag|=c=='-';c=getchar();}
while(isdigit(c)){x=x*10+(c^48);c=getchar();}
x=flag?-x:x;
}
#define mem(a,b) memset(a,b,sizeof(a))
#define ee(i,u) for(int i=head[u];i;i=e[i].next)
/******************************************** Header Template **********************************************/
const int N=110;
int n,rb,cnt,sum;
int a[N],buck[N*N];
int f[N*N];
int minn=INT_MAX;
int main(){
freopen("c.txt","r",stdin);
rd(n);
rep(kase,1,n){
mem(f,0);
mem(a,0);
cnt=0,sum=0;
while(scanf("%d",&a[++cnt]) && a[cnt]!=-1)sum+=a[cnt];
printf("%d 's sum=%d",kase,sum);
puts("");
cnt--;
rb=max(sum,rb);
f[0]=1;
rep(i,1,cnt)
dwn(j,sum,a[i]){
if(f[j-a[i]] && !f[j]){
f[j]=1;
buck[j]++;
}
}
rep(i,1,sum)printf("%d ",buck[i]);
puts("");
}
dwn(i,rb,1)
if(buck[i]==n){
printf("%d
",i);
return 0;
}
printf("%d",rb);
return 0;
}
【two pointers || 断点 || DP】P3143 USACO16OPEN钻石收藏家Diamond Collector
既然是两个互不相交的序列,那么也就是说选取一个断开点,让该断开点左边的数形成一个序列,右边的数形成另外一个序列。
就可以用一种DP的思想,用l[i]记录i左边的最长的可延伸长度(不一定和第i个数有关),r[i]同理。
这样就可以枚举每一个断开点,求它左边的最长长度加上右边的最长长度的和的最大值。
const int N=5e4+10;
int n,k;
int a[N],l[N],r[N];
int main(){
rd(n),rd(k);
rep(i,1,n)rd(a[i]);
sort(a+1,a+n+1);
l[1]=1;
for(int i=2,j=1;i<=n;++i){
while(j<i && a[i]-a[j]>k){
j++;
}
l[i]=max(l[i-1],i-j+1);
}
r[n]=1;
for(int i=n-1,j=n;i;--i){
while(i<j && a[j]-a[i]>k){
j--;
}
r[i]=max(r[i+1],j-i+1);
}
int ans=0;
rep(i,1,n-1)
ans=max(ans,l[i]+r[i+1]);//断开
printf("%d
",ans);
return 0;
}
连通块中点的数量
每次维护siz只用维护树根的siz,合并的时候把作为新的树根节点的siz+=合并的siz就好。
int n,m;
const int N=1e5+10;
int fa[N],siz[N];
inline int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
#undef int
int main(){
#define int long long
rd(n),rd(m);
rep(i,1,n){
fa[i]=i;
siz[i]=1;
}
while(m--){
string op;cin>>op;
int a,b;
if(op=="C"){
rd(a),rd(b);
int fx=find(a),fy=find(b);
if(fx==fy)continue;//如果俩元素已经在同一个集合里面了的话,siz就会翻倍,所以直接continue掉
fa[fx]=fy;
siz[fy]+=siz[fx];
}
else if(op=="Q1"){
rd(a),rd(b);
int fx=find(a),fy=find(b);
if(fx==fy)puts("Yes");
else puts("No");
}
else if(op=="Q2"){
rd(a);
printf("%lld
",siz[find(a)]);
}
}
return 0;
}
substr 练习题 841. 字符串哈希
既然是substr,肯定不是char s[]类型的啊……是string 类型的啦
substr(pos,len):从s[pos]开始复制(从0开始存的),复制的长度为len
string s;
int n,m;
int main(){
rd(n),rd(m);
cin>>s;
while(m--){
int l1,r1,l2,r2;
rd(l1),rd(r1),rd(l2),rd(r2);
string s1,s2;
s1=s.substr(l1-1,r1-l1+1);
s2=s.substr(l2-1,r2-l2+1);
if(s1==s2)printf("Yes
");
else printf("No
");
}
return 0;
}
【带权并查集】P1196 [NOI2002]银河英雄传说
*note:关键在于siz[] 和 dis[]的维护
siz[u]以u为树根的集合大小
dis[u]:u到fa[u]的距离
const int N=3e4+10;
int fa[N],dis[N],T,siz[N];
inline int find(int x){
if(x==fa[x])return x;
int k=fa[x];
fa[x]=find(fa[x]);
dis[x]+=dis[k];
return fa[x];
}
inline int find(int x){
if(x==fa[x])return x;
int k=fa[x];
fa[x]=find(fa[x]);
dis[x]+=dis[k];
return fa[x];
}
inline void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy)return ;
fa[fx]=fy;
dis[fx]=siz[fy];
siz[fy]+=siz[fx];
}
inline int query(int x,int y){
int fx=find(x),fy=find(y);
if(fx!=fy)return -1;
return abs(dis[x]-dis[y])-1;
}
int main(){
#ifdef WIN32
freopen("a.txt","r",stdin);
#endif
rd(T);
rep(i,1,N){
fa[i]=i;
siz[i]=1;
}
while(T--){
char c;cin>>c;
int x,y;rd(x),rd(y);
if(c=='M')
merge(x,y);
else
printf("%d
",query(x,y));
}
return 0;
}
/*
4
M 2 3
C 1 2
M 2 4
C 4 2
*/
//-1
//1
P1197 [JSOI2008]星球大战
- 如果在读入边的时候写while(m--)的话m就会变成-1!!!!你后面如果还要用m的话就不能这样写,所以以后不写while(m--)了,就老老实实写rep(i,1,m)
const int N=4e5+10;
int fa[N],q[N],cnt;
int ans[N];
int n,k,m;
bool vis[N];
struct edge{
int u,v,next;
}e[N<<1];
int head[N],edge_num;
inline void adde(int u,int v){
e[++edge_num].v=v;
e[edge_num].u=u;
e[edge_num].next=head[u];
head[u]=edge_num;
}
inline int find(int x){
return x==fa[x]?x:fa[x]=find(fa[x]);
}
inline void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy)return;
fa[fx]=fy;
}
int main(){
rd(n),rd(m);
rep(i,0,n-1)fa[i]=i;
rep(i,1,m){
int u,v;rd(u),rd(v);
adde(u,v),adde(v,u);
}
rd(k);
cnt=n-k;//假设原来没有摧毁的每个点都不连通
mem(vis,1);
rep(i,1,k){
rd(q[i]);
vis[q[i]]=0;//还没修复
}
rep(i,1,2*m){//判断原来没有摧毁的点有没有连通的,这里直接选择以遍历边的形式来判断,方便快捷
if(vis[e[i].u] && vis[e[i].v] && find(e[i].u)!=find(e[i].v)){
cnt--;
merge(e[i].u,e[i].v);
}
}
ans[k+1]=cnt;
dwn(i,k,1){//倒着加点
int u=q[i];
cnt++;//假设此点不与其他点连通,连通块加一
vis[u]=1;
ee(i,u){
int v=e[i].v;
if(vis[v] && find(u)!=find(v)){
cnt--;
merge(u,v);
}
}
ans[i]=cnt;
}
rep(i,1,k+1)
printf("%d
",ans[i]);
return 0;
}
A 昂贵的字符串
链接:https://ac.nowcoder.com/acm/contest/1093/A来源:牛客网
给出一个字符串S,定义字符串的价值为字符串的长度*字符串中不同字母的个数。但是这里我们将字符的规则改了一下,我们规定字符不区分大小写,即‘A‘=‘a‘,‘B‘=‘b‘...‘Z‘=‘z‘。我们还有一个额外的约束,字母A和字母B是相等的,如字母A=‘c‘,字母B=‘d‘,那么‘c‘,‘C‘,‘d‘,‘D‘也全是等价的,即‘c‘=‘C‘=‘D‘=‘d‘。添加了这些约束后,你能算出字符串的总价值吗?(我答不能你能让我AC吗?)
输入描述:
第一行输入一行一个字符串S第二行两个字母A B,A、B之间用一个空格分隔。
输出描述:
输出一行一个整数,表示字符串的总价值。
示例1
输入
AbC b c
输出
6
备注:
对于所有数据字符串长度1<=|S|<=1000,字符串中只包含大写字母和小写字母。20% 字符串S中不包含小写字母,或不包含大写字母,且A==B60% 字符串S中不包含小写字母,或不包含大写字母100% 无附加约束
tolower是一种函数,功能是把字母字符转换成小写,非字母字符不做出处理。和函数int _ tolower( int c )功能一样,但是_tolower在VC6.0中头文件要用ctype.h。
char s[1010];
bool vis[26];
int cnt;
int main(){
scanf("%s",s);
int len=strlen(s);
char op1,op2;
cin>>op1>>op2;
op1=tolower(op1);
op2=tolower(op2);
for(int i=0;s[i];++i){
s[i]=tolower(s[i]);
if(s[i]==op2)s[i]=op1;
if(!vis[s[i]-'a']){
vis[s[i]-'a']=1;
cnt++;
}
}
printf("%d",len*cnt);
return 0;
}
int n,k;
const int N=1e5+10;
int a[N];
int main(){
#ifdef WIN32
freopen("c.txt","r",stdin);
#endif
rd(n),rd(k);
rep(i,1,n)rd(a[i]);
sort(a+1,a+n+1);
int l=1,r=n;
while(k--){
if(a[l+1]-a[l]>a[r]-a[r-1]){
l++;
}
else
r--;
}
printf("%d",a[r]-a[l]);
return 0;
}
int n,r,Q,mod;
const int N=1e5;
int fac[N+10],invfac[N+10];
inline int ksm(int a,int k,int mod){
int res=1;
for(;k;k>>=1){
if(k&1)
res=res*a%mod;
a=a*a%mod;
}
return res;
}
#undef int
int main(){
#define int long long
#ifdef WIN32
freopen("c.txt","r",stdin);
#endif
rd(Q),rd(mod);
fac[0]=invfac[0]=1;
rep(i,1,N)
fac[i]=fac[i-1]*i%mod;
invfac[N]=ksm(fac[N],mod-2,mod);
dwn(i,N-1,1)
invfac[i]=invfac[i+1]*(i+1)%mod;
while(Q--){
rd(n),rd(r);
printf("%lld
",fac[n]*invfac[r]%mod);
}
return 0;
}
B 最小差值
链接:https://ac.nowcoder.com/acm/contest/1093/B来源:牛客网
给出一个长度为n的数组,你可以去掉数组中最多k个元素,也可以不去掉。你需要通过去掉最多k个元素后使得数组中的最大值-最小值最小化。输出这个值是多少。
输入描述:
第一行两个整数n,k。接下来n个整数a1,a2...an表示数组每个元素的值。
输出描述:
输出一行一个整数表示答案。
示例1
输入
3 1 1 2 4
输出
1
说明
去掉元素4,最小差为2-1=1
备注:
对于所有数据1<=n<=100000,1<=ai<=100000,0<=k<n。10% 保证数组中所有元素的值相等。40% 保证所有元素是一个排列。100% 无附加约束。
首先排序,我们能在两端总共选k个去掉,枚举左边去掉的长度,就可以O(1)求出右边去掉的长度,O(k)扫一遍,用ans记录最小值。
int n,k;
const int N=1e5+10;
int a[N];
int main(){
rd(n),rd(k);
rep(i,1,n)rd(a[i]);
sort(a+1,a+n+1);
int ans=INT_MAX;
rep(len,0,k){
ans=min(ans,a[n-(k-len)]-a[1+len]);
}
printf("%d",ans);
return 0;
}
1557:祖孙询问
询问俩点的祖先关系,可以直接树剖一遍,看另一个点在不在自己的子树范围之内。
const int N=1e5+10;
int n,m,rt;
struct edge{
int v,next;
}e[N<<1];
int head[N],edge_num;
inline void adde(int u,int v){
e[++edge_num].v=v;
e[edge_num].next=head[u];
head[u]=edge_num;
}
int siz[N],fa[N],son[N],deep[N];
inline void dfs1(int u,int f){
fa[u]=f;
siz[u]=1;
deep[u]=deep[f]+1;
ee(i,u){
int v=e[i].v;
if(v==f)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])
son[u]=v;
}
}
int top[N],id[N],chuo=0;
inline void dfs2(int u,int topf){
top[u]=topf;
id[u]=++chuo;
if(!son[u])return;
dfs2(son[u],topf);
ee(i,u){
int v=e[i].v;
if(v==fa[u] || v==son[u])continue;
dfs2(v,v);
}
}
int main(){
#ifdef WIN32
freopen("a.txt","r",stdin);
#endif
rd(n);
rep(i,1,n){
int u,v;rd(u),rd(v);
if(v==-1)rt=u;
else adde(u,v),adde(v,u);
}
dfs1(rt,0);
dfs2(rt,rt);
rd(m);
rep(i,1,m){
int u,v;rd(u),rd(v);
if(id[v]>id[u] && id[v]<=id[u]+siz[u]-1)
puts("1");
else if(id[u]>id[v] && id[u]<=id[v]+siz[v]-1)
puts("2");
else
puts("0");
}
return 0;
}
【bellman_ford】 有边数限制的最短路
- 按层次更新
- 使用backup数组避免串联
inline void BF(){
mem(dis,0x3f);
dis[1]=0;
while(k--){
memcpy(backup,dis,sizeof(dis));
rep(i,1,m){
int u=e[i].u,v=e[i].v,w=e[i].w;
if(dis[v]>backup[u]+w)
dis[v]=backup[u]+w;
}
}
if(dis[n]>0x3f3f3f3f/2)puts("impossible");
else printf("%d
",dis[n]);
}
复习SPFA
vis[]的意义是防止queue离存储重复的点
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=0;
ee(i,u){
int v=e[i].v,w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
}
SPFA 判负环
queue<int>q;
inline int spfa(){
mem(dis,0x3f);
mem(vis,0);
rep(i,1,n){
vis[i]=1;
q.push(i);//由于不是要判断是否有从1到达的负环,而是有没有负环,所以初始的时候直接加入所有的点
}
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=0;
ee(i,u){
int v=e[i].v,w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
cnt[v]=cnt[u]+1;//边数++,
if(cnt[v]>=n)return -1;//边数>=n,说明经过的点数>=n+1,根据容斥原理,有负权环
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
}
}
【string排序 || string类型的ST表 || 也可用trie来排序】 P2412 查单词
- string类型好像只能cincout^^
- 需要自定义比较大小,因为题目上说了对大小写不敏感,而且还要返回原串
- st表离线查询最大值
reference: https://www.luogu.org/blog/lightningUZ/solution-p2412
int n,m;
const int N=5e4+10;
string f[N][25+5];
string max(string a,string b){
string tmp1=a,tmp2=b;
for(int i=0;i<a.size();++i)
a[i]=tolower(a[i]);
for(int i=0;i<b.size();++i)
b[i]=tolower(b[i]);
return a>b?tmp1:tmp2;
}
int main(){
#ifdef WIN32
freopen("a.txt","r",stdin);
#endif
rd(n),rd(m);
rep(i,1,n){
cin>>f[i][0];
}
int k=log2(n);
rep(j,0,k)
for(int i=1;i+(1<<j)-1<=n;++i)
f[i][j+1]=max(f[i][j],f[i+(1<<j)][j]);
rep(i,1,m){
int l,r;rd(l),rd(r);
int len=log2(r-l+1);
cout<<max(f[l][len],f[r-(1<<len)+1][len])<<endl;
}
return 0;
}
以上是关于10.27--11.3 一周刷题记录的主要内容,如果未能解决你的问题,请参考以下文章
周刷题第一期总结(two sum and two numbers)
周刷题第二期总结(Longest Substring Without Repeating Characters and Median of Two Sorted Arrays)