第一次发Codeforces上的题解呢,小C在经过各项权衡之后最终决定打破强迫症而贴一下这个模板。
Description
给定一棵n个结点的树,树上每个结点要么是男孩要么是女孩,每个男孩或女孩都有一个喜欢的数字。m次询问,每次询问两个点,询问这两个点的最短路径之间能找到多少对男女,满足他们喜欢的数字相同。
Input
第一行一个整数n,第二行n个整数表示每个结点是男孩还是女孩,第三行n个整数表示每个结点喜欢的数字fi。接下来n-1行每行表示一条无向边x,y。第n+3行一个整数m。接下来m行每行表示一个询问a,b。
Output
共m行,每行对于每次询问输出题目所要求的答案。
Sample Input
7
1 0 0 1 0 1 0
9 2 9 2 2 9 9
2 6
1 2
4 2
6 5
3 6
7 4
2
1 3
7 5
Sample Output
2
3
HINT
1 ≤ n,m≤ 105,1 ≤ fi ≤ 109,1 ≤ x,y,a,b ≤ n。
Solution
“sb树上莫队。”——小D原话。
碰到这种维护路径上数字个数的询问,我们根本就没有想在线做的欲望,然后我们就有了树上莫队。
最普通的莫队是在数列上做的。所以树上莫队有两种做法——一种是把莫队做法搞到树上,另一种是把树搞成一个数列。
我们选择后者。
如果只是询问子树的话,在树上搞和在数列上搞本质上是相同的,因为一棵子树就代表dfs序上的一段区间。
但如果是询问路径的话,似乎就有点难办,这时我们只要想一想有什么是把路径转成一段区间的方法。
对了!就是括号序列!(不知道括号序列的可以看一看小C的另一篇博客)
设节点x的dfs的开头序为begin[x],结尾序为end[x](其实就是在括号序列中左括号和右括号的位置)。
假设begin[x]<begin[y],这时候路径分为两种情况(继续看下去你就知道为什么要这么分):一种是x为y的祖先,另一种是互相不为祖先。
对于前一种情况,我们只要统计区间[begin[x],begin[y]]里的信息;对于后一种情况,我们只要统计区间[end[x],begin[y]]里的信息。
我们发现x~y这条路径上的点在区间中只被统计了一遍,而其他点被统计了0遍或2遍。
依靠这个性质脑补一下就能写出代码啦!
需要注意的是在第二种情况下,x和y的lca的信息会被遗漏计算。
因为必定有begin[lca]<begin[x]且end[lca]>end[y]。
所以在计算第二种情况时,最后的答案要加上lca对答案的影响。
发生这种情况的原理就是括号序列的定义,每一个括号对应树上的一条有向边。
begin[x]即左括号代表从x的父亲走到x的边,end[x]即右括号代表从x走到x的父亲的边。
所以第二种情况中点数会比边数多1,因为begin[lca]和end[lca]都不属于x~y的路径。
当然如果题目给的是边权而不是点权,就不用考虑这个问题啦!
#include <cstdio> #include <cstring> #include <algorithm> #define ll long long #define MN 200005 #define MK 505 #define MS 19 using namespace std; struct edge{int nex,to;}e[MN]; struct meg{int ld,rd,ldk,pos,lca;}b[MN]; ll ans[MN],sm; int dfbg[MN],dfed[MN],pos[MN],hr[MN],a[MN],lb[MN],dep[MN],sum[MN][2],fa[MS][MN]; int dfn,n,m,bin,pin; bool u[MN],sx[MN]; inline void ins(int x,int y) {e[++pin]=(edge){hr[x],y}; hr[x]=pin;} inline int read() { int n=0,f=1; char c=getchar(); while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();} while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();} return n*f; } void dfs(int x,int fat,int depth) { dfbg[x]=++dfn; pos[dfn]=x; fa[0][x]=fat; dep[x]=depth; for (register int i=hr[x];i;i=e[i].nex) if (e[i].to!=fat) dfs(e[i].to,x,depth+1); dfed[x]=++dfn; pos[dfn]=x; } bool cmp(const meg& a,const meg& b) {return a.ldk<b.ldk || a.ldk==b.ldk && a.rd<b.rd;} inline bool bel(int x,int y) {return dfbg[x]>dfbg[y]&&dfed[x]<dfed[y];} inline void work(int x) { u[pos[x]]^=1; if (u[pos[x]]) ++sum[a[pos[x]]][sx[pos[x]]],sm+=sum[a[pos[x]]][sx[pos[x]]^1]; else --sum[a[pos[x]]][sx[pos[x]]],sm-=sum[a[pos[x]]][sx[pos[x]]^1]; } ll cal(int x,int y,int z) { register int i; for (i=x;i<=y;++i) work(i); ll lt=sm; lt+=sum[a[z]][sx[z]^1]; for (i=x;i<=y;++i) work(i); return lt; } int flca(int x,int y) { if (dep[x]<dep[y]) swap(x,y); for (int k=dep[x]-dep[y],i=0;k;k>>=1,++i) if (k&1) x=fa[i][x]; if (x==y) return x; for (int i=MS-1;i>=0;--i) if (fa[i][x]!=fa[i][y]) x=fa[i][x],y=fa[i][y]; return fa[0][x]; } int main() { register int i,j,jr,x,y,rim; n=read(); for (i=1;i<=n;++i) sx[i]=read(); for (i=1;i<=n;++i) a[i]=lb[i]=read(); sort(lb+1,lb+n+1); bin=unique(lb+1,lb+n+1)-lb-1; for (i=1;i<=n;++i) a[i]=lower_bound(lb+1,lb+bin+1,a[i])-lb; for (i=1;i<n;++i) x=read(),y=read(),ins(x,y),ins(y,x); dfs(1,0,1); for (i=1;i<MS;++i) for (j=1;j<=n;++j) fa[i][j]=fa[i-1][fa[i-1][j]]; m=read(); for (i=1;i<=m;++i) { b[i].ld=read(); b[i].rd=read(); b[i].pos=i; if (dfbg[b[i].ld]>dfbg[b[i].rd]) swap(b[i].ld,b[i].rd); b[i].lca=flca(b[i].ld,b[i].rd); if (b[i].ld==b[i].lca) b[i].lca=0; if (bel(b[i].rd,b[i].ld)) b[i].ld=dfbg[b[i].ld],b[i].rd=dfbg[b[i].rd]; else b[i].ld=dfed[b[i].ld],b[i].rd=dfbg[b[i].rd]; b[i].ldk=(b[i].ld-1)/MK+1; } sort(b+1,b+m+1,cmp); for (i=j=1;i<=m;i=j) { rim=b[i].ldk*MK; for (;b[j].ldk==b[i].ldk&&b[j].rd<=rim;++j) ans[b[j].pos]=cal(b[j].ld,b[j].rd,b[j].lca); for (jr=rim+1;b[j].ldk==b[i].ldk;++j) { for (;jr<=b[j].rd;++jr) work(jr); ans[b[j].pos]=cal(b[j].ld,rim,b[j].lca); } for (--jr;jr>rim;--jr) work(jr); } for (i=1;i<=m;++i) printf("%lld ",ans[i]); }
Last Word
“菜鸡”三人组打Bubble Cup时队友切掉的题。由于是模板题就拿过来做一做。
听说还有一种树分块的做法回头聆听一下教诲。