【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 }

 

转载于:https://www.cnblogs.com/skylynf/p/7144446.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值