[关闭]
@suixinhita 2020-11-19T14:42:45.000000Z 字数 6319 阅读 788

NOIP复习

复习


图论

  1. 拓扑
  2. 最短路(差分约束)
  3. 最小(次小生成树)
  4. lca (倍增,树剖,dfs+rmq)
  5. 强连通,割点,割桥
  6. 二分图(匹配,染色)
  7. 欧拉回路

拓扑

拓扑的作用是将一个图转化成一个序列,有以下几种用法:
· 判断一个序列是否存在(根据条件建图看能不能跑出来)
· 判断是否有环(能不能跑出来,能就没有,不能就有)
· 方便 dp 因为拓扑后没有后效性
· 其他

拓扑的实现

的实现,队列里维护的是入度为 的节点,在访问后将边删除, 访问的序列就是拓扑序,一个正常的拓扑序最后是会把所有的点更新为入度 的。

  1. while(!q.empty())
  2. {
  3. int u=q.front();q.pop();ans[++cnt]='a'+u-1;
  4. for(int i=head[u];i;i=e[i].next)
  5. {
  6. int v=e[i].to;
  7. if(--in[v]==0) q.push(v);
  8. }
  9. }

最短路

spfa

最短路算法,在对负边的处理上有独到的方式, 判负环有奇效。
* 还可以用来搞差分约束,推公式,最大值用最长路,最小值最短路。
时间复杂度不稳

dfs判负环
  1. bool dfs_spfa(int u)
  2. {
  3. vis[u]=1;
  4. for(int i=head[u];i;i=e[i].next)
  5. {
  6. int v=e[i].to;
  7. if(dis[v]>dis[u]+e[i].v)
  8. {
  9. dis[v]=dis[u]+e[i].v;
  10. if(vis[v]) return 0;
  11. if(!dfs_spfa(v)) return 0;
  12. // else return 0;
  13. }
  14. }
  15. vis[u]=0;
  16. return 1;
  17. }

dij

负边不稳,时间稳。主要是重载。

  1. struct node
  2. {
  3. int no,dis;
  4. friend bool operator < (node x,node y)
  5. {
  6. return x.dis>y.dis;
  7. }
  8. };
  9. int head[N];
  10. int dis[N],n,m,s,gs;
  11. void add(int fr,int to,int v)
  12. {
  13. gs++,e[gs].to=to,e[gs].v=v,e[gs].next=head[fr],head[fr]=gs;
  14. }
  15. void dij()
  16. {
  17. priority_queue<node>q;
  18. node ori;
  19. ori.dis=0,ori.no=s;
  20. q.push(ori);
  21. while(!q.empty())
  22. {
  23. node U=q.top();q.pop();
  24. int u=U.no;
  25. for(int i=head[u];i;i=e[i].next)
  26. {
  27. int v=e[i].to;
  28. if(dis[v]>dis[u]+e[i].v)
  29. {
  30. dis[v]=dis[u]+e[i].v;
  31. node V;
  32. V.dis=dis[v],V.no=v;
  33. q.push(V);
  34. }
  35. }
  36. }
  37. }

最小生成树

一个贪心算法,可以用来做许多事情。需要用到并查集,个别时候需要做到重建图。记得分开。

lca

树上dp,树上维护都需要用到,非常基础有用。比较稳妥的是倍增,然后比较狠的是树剖,不修改的是打死也不能写树剖的。

倍增

可以在里面嵌套 .

  1. void add(int fr,int to)
  2. {
  3. gs++;e[gs].to=to,e[gs].next=head[fr],head[fr]=gs;
  4. gs++;e[gs].to=fr,e[gs].next=head[to],head[to]=gs;
  5. }
  6. //双向边注意
  7. int dep[N],fa[N];
  8. bool vis[N];
  9. void dfs(int u,int f)
  10. {
  11. fa[u]=f;vis[u]=1;
  12. dep[u]=dep[f]+1;
  13. for(int i=head[u];i;i=e[i].next)
  14. {
  15. int v=e[i].to;
  16. if(!vis[v]) dfs(v,u);
  17. }
  18. }
  19. int anc[20][N];
  20. void init()
  21. {
  22. for(int i=1;i<=n;++i) anc[0][i]=fa[i];
  23. for(int i=1;i<=19;++i)
  24. for(int j=1;j<=n;++j)
  25. anc[i][j]=anc[i-1][anc[i-1][j]];
  26. }
  27. int query(int x,int y)
  28. {
  29. if(dep[x]<dep[y]) swap(x,y);
  30. int len=dep[x]; /////////// 和RMQ不同的地方。
  31. int lo=-1;
  32. while(len) len>>=1,lo++;
  33. for(int i=lo;i>=0;--i)
  34. if(dep[anc[i][x]]>=dep[y])
  35. x=anc[i][x];
  36. if(x==y) return x;/////
  37. for(int i=lo;i>=0;--i)
  38. if(anc[i][x]!=anc[i][y]) ///////和树剖一样
  39. x=anc[i][x],y=anc[i][y];
  40. return fa[x];
  41. }

树剖

高级不推荐算法,一般出现在 T3 除非 T1 已经 A 了,T2 出不来正解再尝试。主要是要保证线段树和查询的正确性。

两个dfs和查询修改的代码。

  1. void dfs1(int u,int fat)
  2. {
  3. fa[u]=fat;
  4. dep[u]=dep[fat]+1;
  5. size[u]=1;
  6. for(int i=head[u];i;i=e[i].next)
  7. {
  8. int v=e[i].to;
  9. if(v!=fat)
  10. {
  11. dfs1(v,u);
  12. size[u]+=size[v];
  13. if(size[v]>size[son[u]]) son[u]=v;
  14. }
  15. }
  16. }
  17. void dfs2(int u,int tp)
  18. {
  19. top[u]=tp;
  20. pos[u]=++cntp;
  21. if(son[u]) dfs2(son[u],tp);
  22. for(int i=head[u];i;i=e[i].next)
  23. {
  24. int v=e[i].to;
  25. if(v!=fa[u]&&v!=son[u])
  26. dfs2(v,v);
  27. }
  28. }

查询修改(路径)

  1. int max_(int x,int y)
  2. {
  3. if(x==y) return v[x];
  4. int f1=top[x];
  5. int f2=top[y],l,r;
  6. int ans=-inf;
  7. while(f1!=f2)
  8. {
  9. if(dep[f1]<dep[f2])
  10. {
  11. swap(x,y);
  12. swap(f1,f2);
  13. }
  14. l=pos[f1],r=pos[x];
  15. ans=max(ans,T.query_max(l,r));
  16. x=fa[f1],f1=top[x];
  17. }
  18. if(dep[x]<dep[y]) swap(x,y);
  19. l=pos[y],r=pos[x];
  20. ans=max(ans,T.query_max(l,r));
  21. return ans;
  22. }

查询修改(子树)

其实就是一个区间修改,因为无论怎么进行轻重划分,子树肯定是一个连续的序列。

强连通,割点,割桥

强连通是有向图中的,割点割桥是无向图的。
强连通栈里面是点,割点是边,割桥是点。
统一采用 tarjan

强连通

可能会用到缩点重建图,注意图要分开,缩点之后是个DAG,之后大多数会进行一个 dp 。单纯的强连通是太少见了。

  1. void tarjan(int u)
  2. {
  3. low[u]=dfn[u]=++time;
  4. instack[u]=1;stack[++top]=u;
  5. for(int i=head[u];i;i=e[i].next)
  6. {
  7. int v=e[i].to;
  8. if(!dfn[v])
  9. {
  10. tarjan(v);
  11. low[u]=min(low[u],low[v]);
  12. }
  13. else if(instack[v])//****必须要判。
  14. low[u]=min(low[u],low[v]);
  15. }
  16. if(dfn[u]==low[u])
  17. {
  18. int tmp,tot=0;
  19. cnt++;
  20. do
  21. {
  22. tmp=stack[top--];
  23. instack[tmp]=0;
  24. tot++;
  25. }while(tmp!=u);
  26. if(tot==1) cnt--;
  27. }
  28. }

割点

割点的话,注意维护的是边,割点和强连通是一个德行,但是需要判一些返祖情况,建议背一下代码
注意要记录子结点数,存的是边,和返祖边的讨论。

  1. void tarjan(int u,int fa)
  2. {
  3. dfn[u]=low[u]=++time;
  4. int son=0;/////
  5. for(int i=head[u];i;i=e[i].next)
  6. {
  7. int v=e[i].to;
  8. if(!dfn[v])
  9. {
  10. son++;
  11. stack[++top]=i;
  12. tarjan(v,u);
  13. if(low[v]>=dfn[u])
  14. {
  15. int tmp,tmp_u,tmp_v;
  16. cnt++;is_cut[u]=1;
  17. do
  18. {
  19. tmp=stack[top--];
  20. tmp_u=e[tmp].fr;
  21. tmp_v=e[tmp].to;
  22. if(belo[tmp_u]!=cnt) belo[tmp_u]=cnt;
  23. if(belo[tmp_v]!=cnt) belo[tmp_v]=cnt;
  24. }while(tmp_u!=u||tmp_v!=v);
  25. }
  26. else low[u]=min(low[u],low[v]);
  27. }
  28. else if(fa!=v && dfn[v]<dfn[u])
  29. {
  30. low[u]=min(low[u],dfn[v]);///注意这里的dfn,tarjan写dfn和low是一样的,但是这里可能遇到返祖的现象
  31. stack[++top]=i;
  32. }
  33. }
  34. if(fa==0 && son<2) is_cut[u]=0;//高级注意
  35. }

二分图

主要考匹配和染色,匹配就写网络流,染色写dfs。
染色不会单独考,但是考的话一般是用来划分集合的,鉴于只能划分两个集合,其实应该是很好看出来的。

网络流dinic

主要是关注一下数据范围,网络流跑不了很大的数据,还有就是边一定要开够,尽量别开爆。

  1. struct data{
  2. int to,next,f;
  3. }e[M*M*2];////开边注意
  4. int head[N<<1],gs,n,m,e_;
  5. void add(int fr,int to,int f)
  6. {
  7. gs++,e[gs].to=to,e[gs].f=f,e[gs].next=head[fr],head[fr]=gs;
  8. gs++,e[gs].to=fr,e[gs].f=0,e[gs].next=head[to],head[to]=gs;
  9. }
  10. int now[N<<1],dis[N<<1],s,t;
  11. bool bfs()
  12. {
  13. memset(dis,-1,sizeof(dis));
  14. queue<int> q;
  15. dis[s]=0;q.push(s);
  16. while(!q.empty())
  17. {
  18. int u=q.front();q.pop();
  19. for(int i=head[u];i;i=e[i].next)
  20. {
  21. int v=e[i].to;
  22. if(dis[v]==-1&&e[i].f)
  23. dis[v]=dis[u]+1,q.push(v);
  24. }
  25. }
  26. if(dis[t]==-1) return 0;
  27. return 1;
  28. }
  29. int dfs(int u,int f)
  30. {
  31. if(u==t) return f;
  32. int su=0,ff;
  33. for(int i=now[u];i;i=e[i].next)
  34. {
  35. int v=e[i].to;
  36. if(dis[v]!=dis[u]+1) continue;////////不是小是加1
  37. ff=min(e[i].f,f);
  38. ff=dfs(v,ff);
  39. e[i].f-=ff,e[i^1].f+=ff;
  40. su+=ff;f-=ff;
  41. if(e[i].f) now[u]=i;///当前弧的更新不要忘。
  42. if(su==f) return su;//一个小优化
  43. }
  44. if(su==0) dis[u]=-1;
  45. return su;
  46. }
  47. int dinic()
  48. {
  49. int an=0;
  50. while(bfs())
  51. {
  52. for(int i=s;i<=t;++i) now[i]=head[i];
  53. an+=dfs(s,maxx);
  54. }
  55. return an;
  56. }

数论

  1. gcd , exgcd
  2. 欧拉函数,素数筛
  3. 快速幂,矩阵快速幂。(矩阵乘法加法逆元不一样,主要用来优化递推。)

gcd , exgcd

gcd

  1. ll gcd(ll a,ll b)
  2. {
  3. while(a%b)
  4. {
  5. ll t=b;
  6. b=a%b;
  7. a=t;
  8. }
  9. return b;
  10. }

exgcd

  1. ll exgcd(ll a,ll b,ll &x,ll &y)
  2. {
  3. if(b==0){x=1,y=0;return a;}
  4. ll k=exgcd(b,a%b,y,x);
  5. y-=(a/b)*x;
  6. return k;
  7. }

欧拉函数和埃氏筛

埃氏筛

  1. for(int i=2;i<=n;++i)
  2. {
  3. if(!vis[i])
  4. prime[++cnt]=i,is_p[i]=1;
  5. for(int j=1;i*prime[j]<=n&&j<=cnt;++j)//主要是判定
  6. vis[i*prime[j]]=1;
  7. }

欧拉函数

需要记住的3个规律

1.若为质数, ;
2.若为质数,
3.若互质,

意义是比它小的数里面与其互质个数。

  1. void get_tab()
  2. {
  3. for(int i=2;i<=n;++i)
  4. {
  5. if(!vis[i])
  6. {
  7. prime[++cnt]=i;
  8. phi[i]=i-1;
  9. }
  10. for(int j=1;j<=cnt&&i*prime[j]<=n;++j)
  11. {
  12. int f=i*prime[j];vis[f]=1;
  13. if(i%prime[j]==0)
  14. {
  15. phi[f]=phi[i]*prime[j];
  16. break;
  17. }
  18. else phi[f]=phi[i]*(prime[j]-1);
  19. }
  20. }
  21. }

矩阵快速幂

必须给一下矩阵乘法的使用方式————矩阵快速幂优化递推公式转移(注意这个递推公式是加号,只有乘法是不行的,而且一般情况下,转移矩阵是可以直接构造的。

给个例题,包括转移矩阵的构造。
转移公式如下:

%

然后需要做的是构造 的矩阵,然后在 每一个%处变为 1.

给个板子。

  1. struct mar{
  2. ll c[51][51];
  3. mar friend operator *(mar a,mar b)
  4. {
  5. mar y;
  6. for(int i=0;i<k_;++i)
  7. for(int j=0;j<k_;++j)
  8. {
  9. y.c[i][j]=0;
  10. for(int k=0;k<k_;++k)
  11. (y.c[i][j]+=(a.c[i][k]*b.c[k][j]))%=p;
  12. }
  13. return y;
  14. }
  15. };
  16. mar quick_pow(mar x,ll c)
  17. {
  18. mar an;
  19. memset(an.c,0,sizeof(an.c));
  20. for(int i=0;i<k_;++i) an.c[i][i]=1;
  21. for(ll i=c;i;i>>=1,x=x*x)
  22. if(i&1)
  23. an=an*x;
  24. return an;
  25. }

数据结构,没什么好说的,字典树注意一下建立新的节点。

其他的

二分

写成 形式。

离散化

直接复制出来,sort,二分查找。不会被同样的数卡掉。

KMP

话不多说,背吧。

  1. void get_next(char c[],int cl)
  2. {
  3. for(int i=2;i<=cl;++i)
  4. {
  5. int j=fail[i-1];///////////
  6. while(j&&c[i]!=c[j+1]) j=fail[j];
  7. if(c[i]==c[j+1]) j++;//////////
  8. fail[i]=j;
  9. }
  10. }
  11. void check(char f[],char c[],int fl,int cl)
  12. {
  13. get_next(c,cl);
  14. int j=0;
  15. for(int i=1;i<=fl;++i)
  16. { /////////不用找上一个字母的失配点
  17. while(j&&f[i]!=c[j+1]) j=fail[j];
  18. if(f[i]==c[j+1]) j++;
  19. if(j==cl) ans[++ans[0]]=i-cl+1;
  20. }
  21. }

区间dp

先长度后端点,注意判断方式。

背包

注意成立条件就行了。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注