★ 倍增 LCA / k级祖先 / 树上距离 3.1 · 多次问祖先/距离
const int LOG=20; int dep[N],up[N][LOG]; vector<int> g[N];
void bfs(int rt){ queue<int> q; dep[rt]=1; up[rt][0]=0; q.push(rt);
while(!q.empty()){ int u=q.front();q.pop();
for(int v:g[u]) if(!dep[v]){ dep[v]=dep[u]+1; up[v][0]=u;
for(int j=1;j<LOG;j++) up[v][j]=up[up[v][j-1]][j-1]; q.push(v);}}}
int lca(int a,int b){ if(dep[a]<dep[b]) swap(a,b);
for(int j=LOG-1;j>=0;j--) if(dep[up[a][j]]>=dep[b]) a=up[a][j]; //对齐
if(a==b) return a;
for(int j=LOG-1;j>=0;j--) if(up[a][j]!=up[b][j]){a=up[a][j];b=up[b][j];}
return up[a][0]; }
距离=dep[a]+dep[b]-2*dep[lca] · 大数据用BFS防爆栈 · a==b要直接返回 · 同步跳条件是不相等才跳。
★ 倍增维护路径最值(MST路径最大边) 3.4 · 货车运输/P14080
int up[N][LOG],mx[N][LOG]; // mx[v][0]=v到父的边权
// up[v][j]=up[up[v][j-1]][j-1];
// mx[v][j]=max(mx[v][j-1], mx[up[v][j-1]][j-1]);
int pathmax(int a,int b){ int res=0; if(dep[a]<dep[b])swap(a,b);
for(int j=LOG-1;j>=0;j--) if(dep[up[a][j]]>=dep[b]){res=max(res,mx[a][j]);a=up[a][j];}
if(a==b) return res;
for(int j=LOG-1;j>=0;j--) if(up[a][j]!=up[b][j]){
res=max(res,max(mx[a][j],mx[b][j])); a=up[a][j]; b=up[b][j];}
return max(res,max(mx[a][0],mx[b][0])); } //别漏LCA下两条边
套路:先 Kruskal 建(最大)生成树→把树边挂 mx[v][0]→ 两点路径最值用 pathmax。不会写满分就先暴力跳父亲拿部分分。
★ Kruskal + 并查集 (MST) 2.1 · 边表/判连通
int fa[N]; int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
for(int i=1;i<=n;i++) fa[i]=i; // 别忘初始化
sort(e.begin(),e.end()); // 按 w 升序
long long tot=0; int cnt=0;
for(auto&x:e){ int ru=find(x.u),rv=find(x.v);
if(ru!=rv){ fa[ru]=rv; tot+=x.w; cnt++; } }
// cnt==n-1 连通,tot 即答案;否则不连通输出 -1
总权 long long · find不同才合并(防环) · 判环:加边前已同根就成环 · 有向图判环用拓扑,不用并查集。
Dijkstra 堆优化(正权单源) 1.2
typedef pair<long long,int> pli; vector<pli> g[N]; long long dist[N];
priority_queue<pli,vector<pli>,greater<pli> > pq; //小根堆,两>留空格
dist[s]=0; pq.push(make_pair(0LL,s));
while(!pq.empty()){ pli t=pq.top();pq.pop();
long long dd=t.first; int u=t.second;
if(dd>dist[u]) continue; // 过期项必须跳
for(size_t i=0;i<g[u].size();i++){ long long w=g[u][i].first; int v=g[u][i].second;
if(dist[u]+w<dist[v]){ dist[v]=dist[u]+w; pq.push(make_pair(dist[v],v)); }}}
dist 用 long long · 无向图加两遍边 · 八级只用 Dijkstra,别用 auto [d,u](旧评测报错)。
Floyd 全源最短路 1.1 · n≤300~500
const long long INF=1e18; long long d[305][305];
// init d[i][j]=INF,d[i][i]=0,读边取 min(无向加反向)
for(int k=1;k<=n;k++) // k 必须最外层!
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(d[i][k]<INF&&d[k][j]<INF)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
// 传递闭包: f[i][j]|=f[i][k]&&f[k][j];
k 在最外层(写里面就错) · 松弛前判 INF 防溢出 · 重边初始化取 min。
树形DP · 直径/最长链 7
int ans=0; bool vis[N];
int dfs(int u,int p){ vis[u]=1; int d1=0,d2=0; //最长,次长
for(int v:g[u]) if(v!=p){ int d=dfs(v,u);
if(d>d1){d2=d1;d1=d;} else if(d>d2)d2=d; }
ans=max(ans,d1+d2+1); // 过u的路径(节点数)
return d1+1; } // 只把最长链给父亲
// 森林:for(i) if(!vis[i]) dfs(i,0);
过u=d1+d2+1,返回给父亲只 d1+1 · 看清要节点数还是边数(差1)。
树上差分(点/边) 8 · 配 LCA
// 路径 u→v 每点 +d, L=lca(u,v)
diff[u]+=d; diff[v]+=d; diff[L]-=d; diff[fa[L]]-=d; //点差分
// 边差分: diff[u]+=d; diff[v]+=d; diff[L]-=2*d;
int dfs(int u,int p){ int s=diff[u]; // 自底向上汇总
for(int v:g[u]) if(v!=p) s+=dfs(v,u);
val[u]=s; return s; }
点差分扣 fa[L],边差分扣 2*L · 先建好 LCA 的 up/dep/fa。
ST 表(静态区间max/min) 3.5 · 不修改
int st[N][20],lg[N]; // st[i][0]=a[i]; lg[i]=lg[i/2]+1
for(int j=1;j<20;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
int k=lg[r-l+1];
int ans=max(st[l][k], st[r-(1<<k)+1][k]); //右块贴右端
右块起点 r-(1<<k)+1 · 只对 max/min/gcd 成立,求和用前缀和。
组合数 · 快速幂 · 逆元 5/6/3.2
// 杨辉三角(最稳,模数非质数也行,n≤2000)
C[i][0]=1; C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
// 快速幂 a^b % mod
ll power(ll a,ll b,ll m){ ll r=1;a%=m;
while(b){ if(b&1)r=r*a%m; a=a*a%m; b>>=1;} return r;}
// 阶乘逆元(大n,MOD质数): C=fac[n]*inv(fac[m])%MOD*inv(fac[n-m])%MOD
// inv(x)=power(x,MOD-2); 调和筛:for d{for m=d;m+=d} O(KlogK)
MOD 是质数才能用费马逆元;若 MOD=1e9(非质数)→改杨辉/直接整除 · 全程 long long 随时取模。