NOIP复习提纲(持续更新)
1.LCA 预处理(O(nlogn)) 单次查找O(logn)
NKOJ2447
#include<cstdio>
#include<cmath>
#include<iostream>
using namespace std;
int n,s,m;
int End[10010],Last[10010],Next[10010];
int deep[10010];
int f[10010][20];
int du[10010];
void deal_first(int u,int father)
{
deep[u]=deep[father]+1;
for(int i=0;i<=s;i++)
f[u][i+1]=f[f[u][i]][i];
for(int i=Last[u];i;i=Next[i])
{
f[End[i]][0]=u;
deal_first(End[i],u);
}
}
int LCA(int x,int y)
{
if(deep[x]<deep[y]) swap(x,y);
for(int i=s;i>=0;i--)
{
if(deep[f[x][i]]>=deep[y])
x=f[x][i];
if(x==y) return x;
if(deep[x]==deep[y]) break;
}
for(int i=s;i>=0;i--)
{
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
int main()
{
int a,b,x,y;
scanf("%d",&n);
s=ceil(log2(n));
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
End[i]=y;
Next[i]=Last[x];
Last[x]=i;
du[y]++;
}
for(int i=1;i<=n;i++)
{
if(!du[i])
{
deal_first(i,0);
break;
}
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
printf("%d\n",LCA(a,b));
}
return 0;
}
2.ST表 预处理(nlogn) 单次询问O(1)
luogu3865
#include<cstdio>
#include<iostream>
using namespace std;
int n,q,a[100010],p,f[100010][20];
int log[100010];
int main()
{
scanf("%d%d",&n,&q);
log[0]=-1;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
f[i][0]=a[i];
log[i]=log[i>>1]+1;
}
for(int i=1;i<=log[n];i++)
{
for(int j=1;j+(1<<i)-1<=n;j++)
{
f[j][i]=max(f[j][i-1],f[j+(1<<i-1)][i-1]);
}
}
for(int i=1;i<=q;i++)
{
int x,y;
scanf("%d%d",&x,&y);
p=log[y-x+1];
printf("%d\n",max(f[x][p],f[y-(1<<p)+1][p]));
}
return 0;
}
3.树状数组 单次修改O(logn) 单次查询O(logn)
luogu3374
#include<cstdio>
using namespace std;
int n,m;
int a[500010];
int c[500010];
int lowbit(int x)
{
return x&(-x);
}
void modify(int x,int y)
{
for(;x<=n;x+=lowbit(x)) c[x]+=y;
}
int getsum(int x)
{
int ans=0;
for(;x>=1;x-=lowbit(x)) ans+=c[x];
return ans;
}
int main()
{
int k,x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),modify(i,a[i]);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&k,&x,&y);
if(k==1)
modify(x,y);
else
printf("%d\n",getsum(y)-getsum(x-1));
}
return 0;
}
4.线段树
(1).单点修改,区间查询 单次修改O(logn) 单次查询O(logn)
LOJ130
#include<cstdio>
#define ll long long
using namespace std;
ll sum[4000010];
int n,q;
ll a[1000010];
void biuldtree(int p,int l,int r)
{
if(l==r)
{
sum[p]=a[l];
return;
}
int mid=(l+r)>>1;
biuldtree(p<<1,l,mid);
biuldtree(p<<1|1,mid+1,r);
sum[p]=sum[p<<1]+sum[p<<1|1];
}
void change(int p,int l,int r,int x,int y)
{
if(l==r&&l==x)
{
sum[p]+=(ll)y;
return;
}
if(l>x||r<x) return;
int mid=(l+r)>>1;
change(p<<1,l,mid,x,y);
change(p<<1|1,mid+1,r,x,y);
sum[p]=sum[p<<1]+sum[p<<1|1];
}
ll Ask(int p,int l,int r,int x,int y)
{
if(x>r||y<l) return 0;
if(x<=l&&y>=r) return sum[p];
int mid=(l+r)>>1;
ll lsum,rsum;
lsum=Ask(p<<1,l,mid,x,y);
rsum=Ask(p<<1|1,mid+1,r,x,y);
return lsum+rsum;
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
biuldtree(1,1,n);
int k,x,y;
while(q--)
{
scanf("%d%d%d",&k,&x,&y);
if(k==1)
{
change(1,1,n,x,y);
}
else
{
printf("%lld\n",Ask(1,1,n,x,y));
}
}
return 0;
}
(2).区间修改,区间查询 单次修改O(logn) 单次查询O(logn)
LOJ132
#include<cstdio>
#define ll long long
using namespace std;
int n,q;
ll a[1000010];
ll Lazy[4000010],sum[4000010];
void biuldtree(int p,int l,int r)
{
if(l==r)
{
sum[p]=a[l];
return;
}
int mid=(l+r)>>1;
biuldtree(p<<1,l,mid);
biuldtree(p<<1|1,mid+1,r);
sum[p]=sum[p<<1]+sum[p<<1|1];
}
void pushdown(int p,int l,int r)
{
int mid=(l+r)>>1;
Lazy[p<<1]+=Lazy[p];
sum[p<<1]+=(ll)(mid-l+1)*Lazy[p];
Lazy[p<<1|1]+=Lazy[p];
sum[p<<1|1]+=(ll)(r-mid)*Lazy[p];
Lazy[p]=0;
}
void change(int p,int l,int r,int x,int y,int v)
{
if(x>r||y<l) return;
if(x<=l&&y>=r)
{
Lazy[p]+=(ll)v;
sum[p]+=(ll)(r-l+1)*v;
return;
}
if(Lazy[p]) pushdown(p,l,r);
int mid=(l+r)>>1;
change(p<<1,l,mid,x,y,v);
change(p<<1|1,mid+1,r,x,y,v);
sum[p]=sum[p<<1]+sum[p<<1|1];
}
ll Ask(int p,int l,int r,int x,int y)
{
if(x>r||y<l) return 0;
if(x<=l&&y>=r)
{
return sum[p];
}
if(Lazy[p]) pushdown(p,l,r);
int mid=(l+r)>>1;
return Ask(p<<1,l,mid,x,y)+Ask(p<<1|1,mid+1,r,x,y);
}
int main()
{
int k,l,r,x;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
biuldtree(1,1,n);
while(q--)
{
scanf("%d%d%d",&k,&l,&r);
if(k==1)
{
scanf("%d",&x);
change(1,1,n,l,r,x);
}
else
{
printf("%lld\n",Ask(1,1,n,l,r));
}
}
return 0;
}
5.并查集 O(近似常数)
luogu3367
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
int father[200001];
int find(int x)
{
if(father[x]==x) return x;
return father[x]=find(father[x]);
}
void hebing(int x,int y)
{
father[y]=x;
}
int main()
{
int n,m,i,z,x,y,z1,z2;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++) father[i]=i;
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&z,&x,&y);
if(z==1)
{
z1=find(x);
z2=find(y);
if(z1!=z2)
{
hebing(z1,z2);
}
}
else
{
if(find(x)==find(y)) printf("Y\n");
else printf("N\n");
}
}
return 0;
}
6.迪杰斯特拉(边不能有负权) O(mlogn)
NKOJ3639
#include<stdio.h>//这个在比赛中不加,这道题不加好像要被卡
#include<cstdio>
#include<cstring>
#include<queue>
#define ll long long
using namespace std;
struct Node
{
int Num;
ll dis;
bool operator<(const Node &a) const
{
return a.dis<dis;
}
};
priority_queue<Node> q;
int n,m;
ll dis[400010],Len[2000010];
int End[2000010],Next[2000010],Last[400010];
bool mark[400010];
void Dij(int s)
{
int v,u;
Node temp;
temp.Num=s;
temp.dis=0;
q.push(temp);
while(!q.empty())
{
u=q.top().Num;
q.pop();
if(mark[u]) continue;
mark[u]=1;
for(int i=Last[u];i;i=Next[i])
{
v=End[i];
if(!mark[v]&&dis[v]>dis[u]+Len[i])
{
dis[v]=dis[u]+Len[i];
temp.Num=v,temp.dis=dis[v];
q.push(temp);
}
}
}
}
int main()
{
int x,y;
ll z;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%lld",&x,&y,&z);
End[i]=y;
Len[i]=z;
Next[i]=Last[x];
Last[x]=i;
}
scanf("%d%d",&x,&y);
memset(dis,0x3f,sizeof(dis));
dis[x]=0;
Dij(x);
printf("%lld",dis[y]);
return 0;
}
7.Floyd(不能有负权回路) O(n^3)
hdoj1599
#include<bits/stdc++.h>
using namespace std;
int n,m;
int dis[101][101];
int Map[101][101];
int ans;
void floyd()
{
int i,j,k;
for(k=1;k<=n;k++)
{
for(i=1;i<k;i++)
for(j=i+1;j<k;j++)
ans=min(ans,dis[i][j]+Map[i][k]+Map[k][j]);//求最小环
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
int main()
{
while(scanf("%d%d",&n,&m)==2)
{
ans=10000000+100;
int i;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
Map[i][j]=dis[i][j]=10000000+100;
for(i=1;i<=n;i++)
Map[i][i]=dis[i][i]=0;
for(i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
Map[a][b]=Map[b][a]=dis[a][b]=dis[b][a]=min(Map[a][b],c);
}
floyd();
if(ans<10000000+100) printf("%d\n",ans);
else printf("It's impossible.\n");
}
return 0;
}
8.spfa
时间复杂度O(kE)
k指每个点的平均进队次数,一般为2
E指边的总数,所以期望时间复杂度为O(2E)
可用于负权
可判断负权回路:每个点的进队次数不超过N
信息学奥赛一本通1382
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int N=500010<<1;
queue<int> q;
int n,m;
int End[N],Last[N],Next[N],Len[N];
int dis[N];
bool mark[N];
//int cnt[N];
void spfa()
{
int u,v;
mark[1]=1;
q.push(1);
while(!q.empty())
{
u=q.front();
q.pop();
mark[u]=0;
for(int i=Last[u];i;i=Next[i])
{
v=End[i];
if(dis[v]>dis[u]+Len[i])
{
dis[v]=dis[u]+Len[i];
if(!mark[v])
{
mark[v]=1;
q.push(v);
/*cnt[v]++;
if(cnt[v]==n)
{
cout<<"有负环";
exit(0);
}*/
}
}
}
}
}
int main()
{
int a,b,c;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
End[i]=b;
Len[i]=c;
Next[i]=Last[a];
Last[a]=i;
End[i+m]=a;
Len[i+m]=c;
Next[i+m]=Last[b];
Last[b]=i+m;
}
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
//cnt[1]=1;
spfa();
printf("%d",dis[n]);
return 0;
}
9.克鲁斯卡尔 O(mlogm)
luogu3366
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
int father[5010];
int ans,cnt;
struct Node
{
int a,b,Len;
}Edge[200010];
inline bool cmp(Node a,Node b)
{
return a.Len<b.Len;
}
int find(int x)
{
if(father[x]==x) return x;
return father[x]=find(father[x]);
}
inline void merge(int x,int y)
{
father[y]=x;
}
int main()
{
int x,y,z;
int fx,fy;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) father[i]=i;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
Edge[i].a=x,Edge[i].b=y,Edge[i].Len=z;
}
sort(Edge+1,Edge+m+1,cmp);
for(int i=1;i<=m;i++)
{
fx=find(Edge[i].a),fy=find(Edge[i].b);
if(fx!=fy)
{
ans+=Edge[i].Len;
merge(fx,fy);
cnt++;
}
if(cnt==n-1) break;
}
if(cnt==n-1)
printf("%d",ans);
else printf("orz");
return 0;
}
10.prim O(n^2)
luogu3366
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n,m;
int Map[5010][5010];
int dis[5010];
int path[5010];
int ans;
void prim(int x)
{
for(int i=1;i<=n;i++)
{
dis[i]=Map[i][x];
path[i]=x;
}
int Min,flag;
for(int i=1;i<n;i++)
{
Min=0x7fffffff;
for(int j=1;j<=n;j++)
{
if(dis[j]&&Min>dis[j])
{
Min=dis[j];
flag=j;
}
}
dis[flag]=0;
for(int j=1;j<=n;j++)
{
if(dis[j]>Map[j][flag])
{
dis[j]=Map[j][flag];
path[j]=flag;
}
}
}
for(int i=1;i<=n;i++)
if(path[i]!=i) ans+=Map[i][path[i]];
printf("%d",ans);
}
int main()
{
int x,y,z;
scanf("%d%d",&n,&m);
memset(Map,0x3f,sizeof(Map));
for(int i=1;i<=n;i++) Map[i][i]=0;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
Map[x][y]=Map[y][x]=min(Map[x][y],z);
}
prim(1);
return 0;
}
11.Tarjan求有向图强连通分量
(1).在树上从根结点出发做一次DFS,记录每个结点的dfn值和后代的数量。设u有K个后代(不包括u自己)。如果满足:dfn[u] < dfn[v] <= dfn[u] + K 则表明u是v(v不是u)的祖先
信息学奥赛一本通1383
#include<cstdio>
#include<vector>
#include<stack>
using namespace std;
vector<int> G[210];
stack<int> s;
int ans,n,m,visitime,dfn[210],low[210],scc,Belong[210];
bool Instack[210],flag[210];
inline int _min(int a,int b)
{
if(a<b) return a;
return b;
}
inline void Tarjan(int x)
{
dfn[x]=low[x]=++visitime;
s.push(x);
Instack[x]=1;
for(int i=0;i<G[x].size();i++)
{
int j=G[x][i];
if(dfn[j]==0)
{
Tarjan(j);
low[x]=_min(low[x],low[j]);
}
else if(dfn[j]!=0&&Instack[j])
{
low[x]=_min(low[x],dfn[j]);
}
}
if(dfn[x]==low[x])
{
int v;
scc++;
do
{
v=s.top();
s.pop();
Instack[v]=0;
Belong[v]=scc;
}
while(x!=v);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int a;
while(scanf("%d",&a)==1&&a!=0)
{
G[i].push_back(a);
}
}
for(int i=1;i<=n;i++)
{
if(Belong[i]==0)
Tarjan(i);
}
for(int i=1;i<=n;i++)
{
int j;
for(j=0;j<G[i].size();j++)
{
int k=G[i][j];
if(Belong[i]!=Belong[k])
{
flag[Belong[k]]=1;
}
}
}
for(int i=1;i<=scc;i++)
{
if(flag[i]==0) ans++;
}
printf("%d",ans);
return 0;
}
12.最长公共子串
设f[i][j]表示以A串第i个字符和B串第j个字符结尾的最长公共子串(答案:f数组中最大的一个)
if(A[i]==B[j]) f[i][j]=f[i-1][j-1]+1;
else f[i][j]=0;
13.最长公共子序列
设f[i][j]表示A串前i个字符和B串前j个字符结尾的最长公共子序列(答案:f[A.length][B.length])
if(A[i]==B[j]) f[i][j]=f[i-1][j-1]+1;
else f[i][j]=max(f[i-1][j],f[i][j-1]);
14.01矩阵矩阵:在01矩阵中找出一个最大全1正方形
设f[i][j]表示把点(i,j)作为一个正方形对角线(左上往右下)的右下角端点,能够得到的最大正方形的边长
if(a[i][j]==0) f[i][j]=0;
else f[i][j]=min(f[i-1][j-1],f[i-1][j],f[i][j-1])+1;
15.最大子矩阵:在带权(权值可正可负)矩阵中找出一个权值总和最大的矩阵
信息学奥赛一本通1282
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
int sum[101][101],f[101][101][101];
int main()
{
int n,m,i,j,t,k,ans=-0x7fffffff;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
scanf("%d",&t);
sum[i][j]=t+sum[i-1][j];
}
}
for(i=1;i<=n;i++)
{
for(j=i;j<=n;j++)
{
for(k=1;k<=n;k++)
{
t=sum[j][k]-sum[i-1][k];
if(f[i][j][k-1]>0) f[i][j][k]=t+f[i][j][k-1];
else f[i][j][k]=t;
if(ans<f[i][j][k]) ans=f[i][j][k];
}
}
}
printf("%d",ans);
return 0;
}
16.01背包(倒序循环)
(1).使剩余空间最小
f[i]表示背包的剩余体积是否可以为i(f[0]=1,其余为0)
if(f[i-v[j]]) f[i]=1;
(2).使价值最大(不要求用完容积)
f[i]表示容积为i的背包能装下的最大价值(初始化全为0)
f[i]=max(f[i],f[i-v[j]]+value[j])
(3).使价值最大(要求用完容积)
f[i]表示容积为i的背包能装下的最大价值(初始化f[0]=0,其余为负无穷)
f[i]=max(f[i],f[i-v[j]]+value[j])
17.完全背包(正序循环)
(1).使剩余空间最小
f[i]表示背包的剩余体积是否可以为i(f[0]=1,其余为0)
if(f[i-v[j]]) f[i]=1;
(2).使价值最大(不要求用完容积)
f[i]表示容积为i的背包能装下的最大价值
f[i]=max(f[i],f[i-v[j]]+value[j]);
(3).使价值最大(要求用完容积)
参考01背包
18.二维背包
f[i][j]表示第一个背包容积为i,第二个背包容积为j时可获得的最大价值
f[i][j]=max(f[i][j],f[i-v[k]][j]+value[k],f[i][j-v[k]]+value[k]);
19.多重背包
信息学奥赛一本通1269
#include<cstdio>
#include<iostream>
using namespace std;
int v[10001],w[10001],s,f[6001];
int main()
{
int n,m,i,a,b,c,j,k;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
scanf("%d%d%d",&a,&b,&c);
for(j=1;j<=c;j<<=1)
{
s++;
v[s]=j*a;
w[s]=j*b;
c-=j;
}
if(c==0) continue;
s++;
v[s]=a*c;
w[s]=b*c;
}
for(i=1;i<=s;i++)
{
for(j=m;j>=v[i];j--)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
printf("%d",f[m]);
return 0;
}
20.混合背包(如果多重背包要二进制优化则会占用太多空间,时间复杂度只能稍优)
信息学奥赛一本通1270
#include<iostream>
#include<cstdio>
using namespace std;
int w[31],c[31],p[31],f[201];
int main()
{
int n,m,i,j,k,temp;
scanf("%d%d",&m,&n);
for(i=1;i<=n;i++)
scanf("%d%d%d",&w[i],&c[i],&p[i]);
for(i=1;i<=n;i++)
{
if(p[i]==0)
for(j=w[i];j<=m;j++)
f[j]=max(f[j],f[j-w[i]]+c[i]);
else
for(int j=1;j<=p[i];j++)
for(int k=m;k>=w[i];k--)
f[k]=max(f[k],f[k-w[i]]+c[i]);
}
printf("%d",f[m]);
return 0;
}
21.分组背包
信息学奥赛一本通1272
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int w[31],c[31],p,f[201];
vector<int> G[15];
int main()
{
int n,v,t,i,j,k;
scanf("%d%d%d",&v,&n,&t);
for(i=1;i<=n;i++)
scanf("%d%d%d",&w[i],&c[i],&p),G[p].push_back(i);
for(i=1;i<=t;i++)//注意循环阶段
for(j=v;j>=0;j--)
for(k=0;k<G[i].size();k++)
if(j-w[G[i][k]]>=0) f[j]=max(f[j],f[j-w[G[i][k]]]+c[G[i][k]]);
printf("%d",f[v]);
return 0;
}
22.扩展欧几里得
(1).输入a,b,求ax+by=gcd(a,b)中的一组整数解
#include<bits/stdc++.h>
using namespace std;
int a,b,x,y;
int E_gcd(int a,int b,int &x1,int &y1)
{
if(b==0)
{
x1=1,y1=0;
return a;
}
int x2,y2,d=E_gcd(b,a%b,x2,y2);
x1=y2;
y1=x2-a/b*y2;
}
int main()
{
scanf("%d%d",&a,&b);
int d=E_gcd(a,b,x,y);
printf("%d %d",x,y);
return 0;
}
(2).ax+by=c是否有整数解
如果 c%gcd(a,b)==0则有解,否则无解
(3).ax+by=c的通解
设用扩欧求出ax1+by1=gcd(a,b)=d的一组解x1,y1,则(k为常数):
x=x1×c/d+k×b/d
y=y1×c/d-k×a/d
最小正整数解:若gcd(a, b) = d,则方程ax ≡ c (mod b)在[0, b/d - 1]上有唯一解
(4).解模线性方程组:ax≡1(mod b)表示ax%b=1%b
则:ax-by=1 用扩欧处理即可 注意判断是否有解
(5).解模线性方程组:ax≡b (mod n)
即:ax-ny=b 用扩欧处理
(6).解乘法逆元:ax≡1(mod n)的解x称为a在模n意义下的乘法逆元
则:ax-ny=1 所以gcd(a,n)==1才有逆元
注意:通过扩欧算出的解x必须要mod n,即(x+n)%n才是乘法逆元
int check(int a,int n)
{
int x,y;
if(kuoou(a,n,x,y)==1) return (x+n)%n;
return -1;
}
(7).求(a/b) mod p
解:设b1是b在模p意义下的乘法逆元
则(a/b) mod p == (a×b1) mod p == ((a mod p)×(b1 mod p)) mod p
23.质数
(1).费马小定理:如果p是质数且gcd(a,p)==1,则ap-1%p == 1
所以ap-2就是a关于p的逆元
(2).对于一个正整数x,小于x且与x互质的正整数的个数叫做欧拉函数,记做φ(x)(φ(1)==1)
求解:φ(x)
公式1:φ(x)==x*(1-1/p1) * (1-1/p2)… * (1-1/pk)
其中:p1,p2…pk为x的所有质因数
NKOJ3550
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
ll ans;
void phi(ll d)
{
ans=d;
ll i,a=d;
for(i=2;i*i<=a;i++)
{
if(a%i==0)
{
ans-=ans/i;
while(a%i==0)
{
a/=i;
}
}
}
if(a>1) ans-=ans/a;
}
int main()
{
ll n;
scanf("%lld",&n);
phi(n);
printf("%lld",ans);
return 0;
}
公式2:如果x == pk,则φ(x) == (p-1) * (pk-1)(p为质数)
性质1:若gcd(x,y) == 1,则φ(x*y) == φ(x) * φ(y)
性质2:若x为质数,则φ(x) == x-1
线性筛求欧拉函数
void getphi(int n)
{
for(int i=2;i<=n;i++)
{
if(mark[i]==0) prime[++tot]=i,phi[i]=i-1;
else//找一个模板题测一下这个else是否要
{
for(int j=1;j<=tot&&i*prime[j]<=n;j++)
{
mark[i*prime[j]]=1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=prime[j]*phi[i];
break;
}
else
{
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
}
}
(3).欧拉定理:若gcd(a,n) == 1,则aφ(n)%n == 1
应用1:若gcd(a,n) == 1,则 ab%n == ab%φ(n)%n (当b很大且n是质数的时候很好用)
应用2:若b>=φ(n),则ab %n== ab%φ(n)+φ(n)%n
(4).中国剩余定理:
扩展中国剩余定理
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll gcd(ll a,ll b)
{
return b==0?a:gcd(b,a%b);
}
ll exgcd(ll &x,ll &y,ll a,ll b)
{
if(!b)
{
x=1,y=0;
return a;
}
ll x1,y1;
ll d=exgcd(x1,y1,b,a%b);
x=y1,y=x1-a/b*y1;
return d;
}
ll KSC(ll a,ll b,ll c)
{
ll res=0;
while(b)
{
if(b&1)
res=(res+a)%c;
a=(a<<1)%c;
b>>=1;
}
return res;
}
int n;
ll a,b;
ll x,m=1;
int main()
{
ll d,d1;
scanf("%d",&n);
scanf("%lld%lld",&a,&b);
m=a,x=b;
for(int i=1;i<n;i++)
{
ll t,y;
scanf("%lld%lld",&a,&b);
d=exgcd(t,y,m,a);
if((b-x)%d)
{
printf("-1");
return 0;
}
t=KSC(t,((b-x)/d%(a/d)+a/d)%(a/d),a/d);//可以只用%a,但是数大了就会G
x+=t*m;
m=m/gcd(m,a)*a;
}
printf("%lld",(x%m+m)%m);
}
(5).排列:从n个不同的元素中,取m个不重复的元素,按次序排列,称为从n个中取m个的排列。
Amn=n!/(n-m)!
(6).组合:从n个不同的元素中,取m个不重复的元素,不考虑次序,称为从n个中取m个的组合
Cmn=n!/(n-m)!/m!
性质1:Cmn=Cn-mn
性质2:C0n+C1n+C2n…+Cnn=2n
性质3:Cmn=Cmn-1+Cm-1n-1
long long C[1000][1000];
for(int i=1;i<=n;i++)C[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
C[i][j] = (C[i-1][j]+C[i-1][j-1]) % k;
卢卡斯定理:计算Cmn%p(p是质数)
ll C(ll a,ll b)
{
if(a<b) return 0;
if(a==b) return 1;
if(b>a-b) b=a-b;
ll A=1,B=1;
for(ll i=0;i<b;i++)
{
A = (A * (a - i)) % p;
B = (B * (b - i)) % p;
}
return A * quickpow(B,p-2,p) %p;
}
ll Lucas(ll n,ll m)
{
if(m==0) return 1;
return C(n%p,m%p) * Lucas(n/p,m/p) %p;
}
(7).二项式定理:(x+y)n=C0n * xn * y0+C1n * xn-1 * y1+…Cnn * x0 * yn
(8).第二类斯特林数:第二类斯特林数S2[n][m]表示把n个元素划分成m个非空集合的方案数。
S2[n][m] = S2[n-1][m-1] + m * S2[n-1][m]
void getStirling()
{
for(i=1;i<=n;i++)s2[i][1]=1;
for(int i=1;i<=n;i++)
for(int j=2;j<=i&&j<=m;j++)
s2[i][j]=(s2[i-1][j-1]+j*s2[i-1][j])%mod;
}
(9).第一类斯特林数:第一类斯特林数S1[n][m]表示把n个元素划分成m个非空循环排列集合的方案数。
S1[n][m] = S1[n-1][m-1] + (n-1) * S1[n-1][m]
void Stirling1(int n,int m){
S1[1][1]=1;
for(int i=2;i<=n;i++)
for(int j=1;j<=i && j<=m;j++)
S1[i][j]=S[i-1][j-1]+(i-1)*S[i-1][j];}
(10).bell数:BELL数B[n]表示把n个元素划分成若干个非空集合的方案数。
B[n] = S2[n][1]+S2[n][2]+S2[n][3]+ … +S2[n][n]
(11).判断质数之miller_rabin
输入多组数据,每组数据有n个正整数,判断有多少个质数
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,a,ans;
ll quickpow(ll A,ll b,ll c)
{
ll Ans=1;
while(b)
{
if(b&1) Ans=(Ans*A)%c;
A=(A*A)%c;
b>>=1;
}
return Ans;
}
bool Miller_Rabin(ll k)
{
ll d,r=0,x,y,z;
if(k==2) return 1;
if((k&1)==0||k<2) return 0;
d=k-1;
while((d&1)==0)
{
r++;
d/=2;
}
for(int i=1;i<=10;i++)
{
z=rand()%(k-2)+2;
x=quickpow(z,d,k);
for(ll j=1;j<=r;j++)
{
y=(x%k*(x%k))%k;
if(y==1&&x!=1&&x!=k-1) return 0;
x=y;
}
if(x!=1) return 0;
}
return 1;
}
int main()
{
while(scanf("%lld",&n)==1)
{
ans=0;
for(ll i=1;i<=n;i++)
{
scanf("%lld",&a);
if(Miller_Rabin(a)) ans++;
}
printf("%lld\n",ans);
}
return 0;
}
24.归并排序
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
int a[500010],b[500010];
long long ans;
int n;
void my_sort2(int l,int mid,int r)
{
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r)
{
if(a[i]<=a[j])
{
b[k]=a[i];
k++;
i++;
}
else
{
b[k]=a[j];
k++;
ans+=(long long)mid-i+1;
j++;
}
}
while(i<=mid)
{
b[k]=a[i];
k++;
i++;
}
while(j<=r)
{
b[k]=a[j];
k++;
j++;
}
for(i=l;i<=r;i++) a[i]=b[i];
}
void my_sort1(int l,int r)
{
if(l==r) return;
int mid=(l+r)>>1;
my_sort1(l,mid);
my_sort1(mid+1,r);
my_sort2(l,mid,r);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
my_sort1(1,n);
printf("%lld",ans);
return 0;
}
25.快速幂
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll A,B,C;
ll quickpow(ll a,ll b,ll c)
{
ll ans=1;
for(;b;b>>=1)
{
if(b&1) ans=ans*a%c;
a=a*a%c;
}
return ans;
}
int main()
{
scanf("%lld%lld%lld",&A,&B,&C);
ll ans=quickpow(A,B,C);
printf("%lld",ans);
return 0;
}
26.枚举子集(s1是s的子集)
for(int s1=s;s1;s1=(s1-1)&s)
{
//do something
}
27.高精度
(1).加
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
string s,y;
string jiafa(string x,string y)
{
int i,l1=x.length(),l2=y.length(),a[201],b[201],c[202],flag=0,x1=0;
string s1="";
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
for(i=0;i<l1;i++) a[l1-i]=int(x[i]-48);
for(i=0;i<l2;i++) b[l2-i]=int(y[i]-48);
i=1;
while(i<=l1||i<=l2)
{
c[i]=a[i]+b[i]+x1;
x1=c[i]/10;
c[i]%=10;
i++;
}
c[i]=x1;
if(c[i]==0) i--;
for(;i>=1;i--)
{
s1+=char(c[i]+48);
}
return s1;
}
int main()
{
cin>>s>>y;
string s1=jiafa(s,y);
cout<<s1;
return 0;
}
(2).减
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
void jianfa(char x[201],char y[201])
{
int i,lx=strlen(x),ly=strlen(y),a[201],b[201],c[201],c1=0;
char z[201];
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
if(lx==ly&&strcmp(x,y)==-1||lx<ly)
{
strcpy(z,x);
strcpy(x,y);
strcpy(y,z);
printf("-");
}
lx=strlen(x);
ly=strlen(y);
for(i=0;i<lx;i++) a[lx-i]=int(x[i]-48);
for(i=0;i<ly;i++) b[ly-i]=int(y[i]-48);
i=1;
while(i<=lx||i<=ly)
{
if(a[i]-b[i]<0)
{
a[i]+=10;
a[i+1]--;
}
c[i]=a[i]-b[i];
i++;
}
while(c[i]==0&&i>1) i--;
for(;i>=1;i--)
printf("%d",c[i]);
}
int main()
{
char a[201],b[201];
scanf("%s%s",&a,&b);
jianfa(a,b);
return 0;
}
(3).乘
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
void jiafa(char x[201],char y[201])
{
int i,a[201],b[201],c[202],x1=0,lx=strlen(x),ly=strlen(y),j;
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
for(i=0;i<lx;i++) a[lx-i]=int(x[i]-48);
for(i=0;i<ly;i++) b[ly-i]=int(y[i]-48);
for(i=1;i<=lx;i++)
{
x1=0;
for(j=1;j<=ly;j++)
{
c[i+j-1]+=a[i]*b[j]+x1;
x1=c[i+j-1]/10;
c[i+j-1]%=10;
}
c[i+ly]=x1;
}
i=lx+ly;
while(c[i]==0&&i>1) i--;
for(;i>=1;i--)
printf("%d",c[i]);
}
int main()
{
char x[201],y[201];
scanf("%s%s",&x,&y);
jiafa(x,y);
return 0;
}