[BZOJ 3631] 松鼠的新家
Link:
Solution:
这题一眼看上去是裸的树剖,但实际上完全没有必要进行区间加的操作
由于不需要在线的查询,我们可以直接用差分数组来解决此题
而这又有两种方式:
1、先轻重链剖分,按树剖更新时一样处理差分数组
对于每一条连续的部分执行$res[pos[top[x]]]++$和$res[pos[x]+1]--$
注意除去每个点的重复计算和对起点及终点的特殊处理!
2、直接对$res[dat[i]]++$和$res[dat[i+1]]++$,$res[lca]--$和$res[father[lca]]--$
最后从底至上进行更新即可
注意$LCA$处重复算了一次,要减一,而$father[LCA]$还要再减一才能满足差分
二者复杂度差不多,但(2)好像更妙一点
Solution1:
#include <bits/stdc++.h> using namespace std; const int MAXN=3e5+10; int siz[MAXN],top[MAXN],dep[MAXN],pos[MAXN],f[MAXN][20],dat[MAXN],n,res[MAXN],cnt=0; vector<int> G[MAXN]; void dfs1(int k,int anc) { siz[k]=1; for(int i=1;(1<<i)<=dep[k];i++) f[k][i]=f[f[k][i-1]][i-1]; for(int i=0;i<G[k].size();i++) { int v=G[k][i]; if(anc==v) continue; f[v][0]=k;dep[v]=dep[k]+1; dfs1(v,k);siz[k]+=siz[v]; } } void dfs2(int k,int up) //树剖的处理 { int bs=0;pos[k]=++cnt;top[k]=up; for(int i=0;i<G[k].size();i++) if(G[k][i]!=f[k][0] && siz[G[k][i]]>siz[bs]) bs=G[k][i]; if(!bs) return; dfs2(bs,up); for(int i=0;i<G[k].size();i++) if(G[k][i]!=f[k][0] && G[k][i]!=bs) dfs2(G[k][i],G[k][i]); } void LCA(int x,int y) { while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]]) swap(x,y); res[pos[top[x]]]++;res[pos[x]+1]--;x=f[top[x]][0]; } if(dep[x]<dep[y]) swap(x,y); res[pos[y]]++;res[pos[x]+1]--; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&dat[i]); for(int i=1;i<n;i++) { int x,y;scanf("%d%d",&x,&y); G[x].push_back(y);G[y].push_back(x); } dfs1(1,0);dfs2(1,1); for(int i=1;i<=n;i++) { if(i==1){res[pos[dat[i]]]++;res[pos[dat[i]]+1]--;continue;} LCA(dat[i-1],dat[i]);res[pos[dat[i-1]]]--;res[pos[dat[i-1]]+1]++; //减去对每个点重复算的一次 if(i==n){res[pos[dat[i]]]--;res[pos[dat[i]]+1]++;} } for(int i=1;i<=cnt;i++) res[i]+=res[i-1]; for(int i=1;i<=n;i++) printf("%d\n",res[pos[i]]); return 0; }
Solution2:
#include <bits/stdc++.h> using namespace std; const int MAXN=3e5+10; int siz[MAXN],dep[MAXN],f[MAXN][20],dat[MAXN],n,res[MAXN]; vector<int> G[MAXN]; void dfs(int k,int anc) { for(int i=1;(1<<i)<=dep[k];i++) f[k][i]=f[f[k][i-1]][i-1]; for(int i=0;i<G[k].size();i++) { int v=G[k][i]; if(anc==v) continue; f[v][0]=k;dep[v]=dep[k]+1;dfs(v,k); } } int LCA(int x,int y) { if(dep[x]<dep[y]) swap(x,y); int t=dep[x]-dep[y]; for(int i=0;i<=18;i++) if(t&(1<<i)) x=f[x][i]; for(int i=18;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; if(x==y) return x; return f[x][0]; } void cnt(int k) //从底至上更新 { for(int i=0;i<G[k].size();i++) { int v=G[k][i]; if(v==f[k][0]) continue; cnt(v);res[k]+=res[v]; } } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&dat[i]); for(int i=1;i<n;i++) { int x,y;scanf("%d%d",&x,&y); G[x].push_back(y);G[y].push_back(x); } dfs(dat[1],0); for(int i=2;i<=n;i++) { int lca=LCA(dat[i-1],dat[i]); res[dat[i]]++;res[dat[i-1]]++; res[lca]--;res[f[lca][0]]--; } cnt(dat[1]); for(int i=2;i<=n;i++) res[dat[i]]--; //减去重复的次数 for(int i=1;i<=n;i++) printf("%d\n",res[i]); return 0; }
Review:
这题竟然是第一次写树上差分,
如果仅仅是两点间修改,而没有在线查询的话,直接差分的常数是要比区间加的效率高得多的
因此能用差分还是用差分
记住如果不是树剖的差分是从底至上更新的