[TOC]数组
CSP前把重要的板子所有打一遍吧,顺便放一些下饭集锦(在每一个板子的下面)数据结构
图论
SPFA
ll dis[N]; bool vis[N]; queue<int> q; void spfa(int s) { memset(vis,0,sizeof(vis)); memset(dis,0x3f,sizeof(dis)); q.push(s);vis[s]=1;dis[s]=0; while(!q.empty()) { int u=q.front();q.pop();vis[u]=0; for(register int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(dis[v]>dis[u]+edge[i].dis) { dis[v]=dis[u]+edge[i].dis; if(!vis[v]) { q.push(v); vis[v]=1; } } } } }
注意事项: 没什么好注意的,spfa极其优美,手感温馨; 下饭集锦: 脑子一抽把v搞成了edge[i].nextspa
Dijkstra
struct Node { int u; ll dis; bool operator < (const Node &a) const { return a.dis<dis; } }; ll dis[N]; bool vis[N]; priority_queue<Node> q; void dijkstra(int s) { memset(vis,0,sizeof(vis)); memset(dis,0x7f,sizeof(dis)); q.push(Node{s,0});dis[s]=0; while(!q.empty()) { int u=q.top().u;q.pop(); if(vis[u]) continue; vis[u]=1; for(register int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(dis[v]>dis[u]+edge[i].dis) { dis[v]=dis[u]+edge[i].dis; q.push(Node{v,dis[v]}); } } } }
注意事项: 第一个节点不要更新vis标记,在循环的时候再判断和更新; 取出优先队列元素的时候也不用清空标记; 自定义Node的时候别写反了…… 下饭集锦: 跑最短路,我写的大根堆……code
LCA
int anc[N][25]; int fa[N],dep[N]; void dfs(int u) { anc[u][0]=fa[u]; for(register int i=1;i<=20;++i) anc[u][i]=anc[anc[u][i-1]][i-1]; for(register int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(v==fa[u]) continue; fa[v]=u; dep[v]=dep[u]+1; dfs(v); } } int lca(int x,int y) { if(dep[x]<dep[y]) swap(x,y); int delta=dep[x]-dep[y]; for(register int i=0;delta;delta>>=1,++i) if(delta&1) x=anc[x][i]; if(x==y) return x; for(register int i=20;anc[x][0]!=anc[y][0];--i) { if(anc[x][i]!=anc[y][i]) { x=anc[x][i]; y=anc[y][i]; } } return anc[x][0]; }
注意事项: 预处理后,先保证深度x>=y,而后delta倍增往下跳,注意第一重循环从1开始! 下饭集锦: for(register int i=0;delta;delta>>=1,++i)写成了for(register int i=0;delta>>=1;++i) 结果就是进入循环的时候就已经除以2了qwq队列
树的直径
树的直径能够两次dfs很简单地求出来,这儿放一个树形dp的方法,实在没记住就dfs吧ci
void dfs(int u) { sum1=sum2=0; for(register int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(v==fa[u]) continue; fa[v]=u; sum2=max(sum2,dfs(v)+edge[i].dis); if(sum2>sum1) swap(sum2,sum1); } ans=max(ans,sum1+sum2); return ans; }
注意事项: sum2维护的是当前次大值,更新完后若是变成最大值了记得交换一下 下饭集锦: 没有下饭qwqit
二分图
int match[N]; bool used[N]; bool dfs(int pos) { for(register int i=1;i<=m;++i) { if(vis[pos][i]&&!used[i])//这条边存在且没用过 { used[i]=1; if(!match[i]||dfs(match[i]))//若是还没匹配或能够从新匹配 { match[i]=pos; return true; } } } return false; }
注意事项: 对左边的每一个点进行搜索前,要清空used数组 下饭集锦: 无io
Kruskal
void kruskal() { sort(edge+1,edge+m+1,cmp); for(register int i=1;i<=n;++i) fa[i]=i; for(register int i=1;i<=m;++i) { int fx=find(edge[i].from); int fy=find(edge[i].to); if(fx==fy) continue; fa[fx]=fy; ans+=edge[i].dis; if(++num==n-1) break; } }
注意事项: kruskal还有什么值得注意的么…… 下饭集锦: 无class
数论
线性筛
int prime[N],num; bool notprime[N]; void Ls(int maxn) { for(register int i=2;i<=maxn;++i) { if(!notprime[i]) prime[++num]=i; for(register int j=1;j<=num&&prime[j]*i<=maxn;++j) { notprime[prime[j]*i]=1; if(!(i%prime[j])) break; } } }
注意事项: j从1开始枚举,乘的时候注意边界 下饭集锦: 没有特判1!注意特判notprime[1]=1啊!!!qwqsed
乘法逆元
若是mod是质数,能够直接费马小定理求逆元
ll qpow(int n,int k) { ll res=1; while(k) { if(k&1) res=(res*n)%mod; n=(n*n)%mod; k>>=1; } return res; } ll inv(int n) { return qpow(n,mod-2); }
当要求不少连续的逆元时,能够线性递推求解(二式是阶乘逆元) $$inv[i]=inv[mod%i]×(mod-\frac{mod}{i})$$
$$facinv[i]=facinv[i+1]*(i+1)$$
void init() { inv[1]=1; for(register int i=2;i<=n;++i) inv[i]=inv[mod%i]*(mod-mod/i)%mod; }
若是不保证mod是质数,但n和mod互质,就能够exgcd求解了
ll x,y; void exgcd(int a,int b) { if(!b) {x=1,y=0;return;} exgcd(b,a%b); int z=x;x=y;y=z-a/b*y; } int main() { read(n);read(mod); exgcd(n,mod); printf("%lld\n",(x%mod+mod)%mod); }
注意事项: 线性递推的时候$mod-\dfrac{mod}{i}$千万不要提mod变成$mod*\dfrac{i-1}{i}$,否则永远是0(笑); exgcd记不住就从新推一遍,$x->y\ ,\ y->x-\lfloor{\dfrac{a}{b}}\rfloor *y$ 下饭集锦: 我还真把mod给提公约数了,而后逆元全是0……
其余
ST表
int main() { read(n);read(m); for(register int i=1;i<=n;++i) read(Max[i][0]); Lg[0]=-1; for(register int i=2;i<=100002;++i) Lg[i]=Lg[i/2]+1; for(register int j=1;j<=25;++j) for(register int i=1;i+(1<<j)-1<=n;++i) Max[i][j]=max(Max[i][j-1],Max[i+(1<<(j-1))][j-1]); for(register int i=1;i<=m;++i) { read(l);read(r); int LG=Lg[r-l+1]; printf("%d\n",max(Max[l][LG],Max[r-(1<<LG)+1][LG])); } return 0; }
注意事项: 没啥好注意的,最多就是判边界的时候当心点吧 下饭集锦: 无
数据结构
树状数组
void add(int pos,int v) { while(pos<=n) { b[pos]+=v; pos+=lowbit(pos); } } ll query(int pos) { ll res=0; while(pos) { res+=b[pos]; pos-=lowbit(pos); } return res; }
注意事项: 加的时候逐步加lowbit,询问的时候逐步减lowbit(废话) 下饭集锦: 若是树状数组都下饭我就没了啊……
分块
int query(int l,int r,int x) { int p=pos[l],q=pos[r],res=0; if(p==q) { for(register int i=l;i<=r;++i) if(a[i]==x) res++; return res; } for(register int i=p+1;i<=q-1;++i) res+=b[i][x]; for(register int i=l;i<=R[p];++i) if(a[i]==x) res++; for(register int i=L[q];i<=r;++i) if(a[i]==x) res++; return res; } int main() { num=sqrt(n); for(register int i=1;i<=num;++i) { L[i]=(i-1)*num+1; R[i]=i*num; } if(R[num]<n) num++,L[num]=R[num-1]+1,R[num]=n; for(register int i=1;i<=num;++i) for(register int j=L[i];j<=R[i];++j) pos[j]=i,b[i][a[j]]++; }
注意事项: 若是初始分块没有覆盖全部点,把最后几个点也放入块中 下饭集锦: 无