【BZOJ2783/JLOI2012】树-树上倍增
Problem 树
题目大意
给出一棵树,求这个树上的路径的数量,要求路径上的点权和等于s且路径的上每个点深度不同。
Solution
这个题目可以用不少方法做。
首先,路径上每个节点的深度不同决定了这条链上每一个节点的上一个节点都是他父亲。于是就可以开始乱搞了。
对于每一个节点,我们认为这个节点为路径尾节点,然后去查询其父亲链上是否有路径点权和为s的节点。
我们可以用c++自带的set去存储前缀和,对于每个点查询,这样复杂度为O(nlogn)
这应该是最快的一种方法。然而我这个蒟蒻并不是很熟悉c++的set。。这种红黑树太强了orz。
那么接下来我们就可以用倍增的方法去做。这样的时间复杂度为两个log(O(nlog^2n))
我们设立一个fa数组,其fa[i][j]表示对于i节点,向上的2^j个节点编号是什么。显而易见,fa[i][0]就是i的父亲。
然后我们还需要一个储存点权和的数组,设立w数组,其中w[i][j]表示对于i节点,向上2^j个节点的权值和
(不包括2^j这个节点但是包括i本身)显而易见,w[i][0]就表示i节点本身的点权。
fa[i][j]=fa[fa[i][j-1][j-1]
w[i][j]=w[fa[i][j-1]][j-1]+w[i][j-1]
可以看出,这两个数组在O(n)的时间就可以求出来了。
接下来开始计算过程,我们对于每一个节点i,设立临时节点nx。nx初始化为i;
我们设k为20,从20->1,对于w[nx][k],若其小于s,则s-=w[nx][k],nx=fa[nx][k];
如此倍增向上跳,若是跳完的时候权值和正好等于s,那么就对ans贡献1,即ans++
计算完每个点贡献以后即为答案。
AC Code
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 struct node{ 6 int to,next; 7 }e[200010]; 8 int fa[100010][21],w[100010][21],h[100010],u,v,n,s,a[100010],tot=0; 9 long long ans=0; 10 void dfs(int x,int last){ 11 fa[x][0]=last; 12 w[x][0]=a[x]; 13 for(int i=1;i<=20;i++) 14 fa[x][i]=fa[fa[x][i-1]][i-1],w[x][i]=w[x][i-1]+w[fa[x][i-1]][i-1]; 15 int nx=x,now=s; 16 for(int i=20;i>=0;i--) 17 if(fa[nx][i]&&w[nx][i]<now)now-=w[nx][i],nx=fa[nx][i]; 18 if(w[nx][0]==now)ans++; 19 for(int i=h[x];~i;i=e[i].next)if(e[i].to!=last)dfs(e[i].to,x); 20 } 21 int main(){ 22 // freopen("bzoj2783.in","r",stdin); 23 memset(h,-1,sizeof(h)); 24 scanf("%d%d",&n,&s); 25 for(int i=1;i<=n;i++)scanf("%d",&a[i]); 26 for(int i=1;i<n;i++){ 27 scanf("%d%d",&u,&v); 28 e[++tot].to=v;e[tot].next=h[u];h[u]=tot; 29 e[++tot].to=u;e[tot].next=h[v];h[v]=tot; 30 } 31 dfs(1,0); 32 printf("%lld",ans); 33 }