bzoj3572: [Hnoi2014]世界树
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3572
思路:注意到m[1]+m[2]+…+m[q]<=300000
上虚树Dp。
先DP出虚树上每个点被哪个点管辖,记为bel[i]。
这个从上到下更新一次答案,在从下到上更新一次答案即可。
对于最终答案,我们遍历一遍虚树,把虚树每条边对应的点划分好即可
然后我们考虑虚树的一条边(a,b)
记x为既是a的儿子又是b的祖先的点,siz[a]表示a点的子树大小
1.如果bel[a]==bel[b]那么这条边所对应的一堆实际的点也应该归bel[a]管辖
那么f[bel[a]]+=siz[x]-siz[b]
2.如果bel[a]!=bel[b]那么这条边(在原树上是一条路径)中一定有一个分界点mid
使得mid及以下的点归bel[b]管辖,mid以上的点归bel[a]管辖
倍增找出mid即可
那么f[bel[a]]+=siz[x]-siz[mid],f[bel[b]]+=siz[mid]-siz[b]
最后要处理的是完全没有在虚树上出现的子树,这些子树肯定被控制它们的根的点所控制
我们记录一个rem[i]表示i的子树现在还有多少个点没被统计答案,初值为siz[i]
处理了一条边(a,b)后,那么x的子树就在处理虚树时已经被我们处理完了,记得减去,rem[a]-=siz[x]
最后把这些多余的点加回去即可,f[bel[a]]+=rem[a]
然后有一个细节,记得把虚树的根的rem设为整个树的大小,而不是它的siz,因为那样会使我们漏掉原来的根的其他子树
实现的时候注意细节(各种乱七八糟的问题)....
不知道为什么这么慢....(看来是写丑了)
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=300010,maxm=600010,maxk=22; using namespace std; int n,m,fa[maxn][maxk],dep[maxn],siz[maxn],dfn[maxn],tim,f[maxn],cnt,poi[maxn],bel[maxn],stk[maxn],top,ordc,ord[maxn],seq[maxn],rem[maxn]; bool bo[maxn];char ch; bool cmp(int a,int b){return dfn[a]<dfn[b];} void read(int &x){ for (ch=getchar();!isdigit(ch);ch=getchar()); for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; } int lca(int a,int b){ if (dep[a]<dep[b]) swap(a,b); for (int h=dep[a]-dep[b],i=19;i>=0;i--) if (h>=(1<<i)) h-=(1<<i),a=fa[a][i]; if (a==b) return a; for (int i=19;i>=0;i--) if (fa[a][i]!=fa[b][i]) a=fa[a][i],b=fa[b][i]; return fa[a][0]; } int getd(int a,int b){return dep[a]+dep[b]-(dep[lca(a,b)]<<1);} struct Tgraph{ int pre[maxm],now[maxn],son[maxm],tot; void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;} void dfs1(int x){ siz[x]=1,dfn[x]=++tim; for (int i=1;i<=19;i++) fa[x][i]=fa[fa[x][i-1]][i-1]; for (int y=now[x];y;y=pre[y]) if (son[y]!=fa[x][0]) dep[son[y]]=dep[x]+1,fa[son[y]][0]=x,dfs1(son[y]),siz[x]+=siz[son[y]]; } void dfs2(int x){//找子树中最近的关键点 ord[++ordc]=x,rem[x]=siz[x]; for (int y=now[x];y;y=pre[y]){ int v=son[y];dfs2(v); if (!bel[v]) continue; int t1=getd(x,bel[v]),t2=getd(x,bel[x]); if (!bel[x]||t1<t2||(t1==t2&&bel[v]<bel[x])) bel[x]=bel[v]; } } void dfs3(int x){//找上面最近的关键点 for (int y=now[x];y;y=pre[y]){ int v=son[y],t1=getd(v,bel[x]),t2=getd(bel[v],v); if (!bel[v]||t1<t2||(t1==t2&&bel[x]<bel[v])) bel[v]=bel[x]; dfs3(v); } } void solve(int a,int b){//处理虚边a->b int x=b,mid=b; for (int i=19;i>=0;i--) if (dep[fa[x][i]]>dep[a]) x=fa[x][i]; rem[a]-=siz[x]; if (bel[a]==bel[b]){f[bel[a]]+=siz[x]-siz[b];return;} for (int i=19;i>=0;i--){ int nxt=fa[mid][i]; if (dep[nxt]<=dep[a]) continue; int t1=getd(bel[a],nxt),t2=getd(bel[b],nxt); if (t2<t1||(t1==t2&&bel[b]<bel[a])) mid=nxt; } f[bel[a]]+=siz[x]-siz[mid],f[bel[b]]+=siz[mid]-siz[b]; } void getans(){ for (int i=1;i<=ordc;i++) for (int y=now[ord[i]];y;y=pre[y]) solve(ord[i],son[y]); for (int i=1;i<=ordc;i++) f[bel[ord[i]]]+=rem[ord[i]]; for (int i=1;i<=cnt;i++) printf("%d ",f[seq[i]]);puts(""); } }g1,g2; void work(){ stk[top=1]=poi[1]; for (int i=2;i<=cnt;i++){ int u=lca(poi[i],stk[top]); while (dfn[stk[top]]>dfn[u]){ if (dfn[stk[top-1]]<=dfn[u]){ g2.add(u,stk[top]); if (u!=stk[--top]) stk[++top]=u; break; } g2.add(stk[top-1],stk[top]),top--; } stk[++top]=poi[i]; } while (top>1) g2.add(stk[top-1],stk[top]),top--; g2.dfs2(stk[1]),g2.dfs3(stk[1]),rem[stk[1]]=siz[1],g2.getans(); for (int i=1;i<=ordc;i++){int p=ord[i];f[p]=g2.now[p]=rem[p]=bel[p]=0;} g2.tot=ordc=0; } int main(){ read(n); for (int i=1,a,b;i<n;i++) read(a),read(b),g1.add(a,b),g1.add(b,a); g1.dfs1(1),read(m); while (m--){ read(cnt); for (int i=1;i<=cnt;i++) read(poi[i]),seq[i]=poi[i],bo[poi[i]]=1,bel[poi[i]]=poi[i]; sort(poi+1,poi+1+cnt,cmp),work(); } return 0; } /* 10 2 1 3 2 4 3 5 4 6 1 7 3 8 3 9 4 10 1 5 2 6 1 5 2 7 3 6 9 1 8 4 8 7 10 3 5 2 9 3 5 8 */