2018.07.22 洛谷P3047附近的牛(树形dp)

27 篇文章 1 订阅
8 篇文章 0 订阅

传送门
给出一棵 n n 个点的树,每个点上有Ci头牛,问每个点 k k 步范围内各有多少头牛。
刚看完题惊了这东西不可做啊。
然后就开始想换根dp,结果没杠出来。
继续读题发现 k k 很小啊,才20,那这怕不是可以跑一个 Onk O ( n k ) 的算法哦。
然后发现确实可以 Onk O ( n k ) 做出来,方法是这样的。
我们仍然先选一个节点(为了方便我选的就是 1 1 )当根节点,然后用siz[p][k]表示出以每个 p p 作为根节点的子树中距离p不大于 k k 的节点的权值和,子树之外的节点都先不管。这个东西是可以先通过儿子的信息先转移出每个p作为根节点的子树中距离 p p 等于k的节点的权值和,然后再跑一边前缀和求出来的,时间复杂度 Onk O ( n k ) ,然后对于每一个点 u u ,以它作为整个树的根节点时,整棵树的贡献就是沿着u 1 1 的链向上跳,跳到跳不动或者已经跳了k步停止,跳的时候用简单的容斥原理统计答案(其实就是累加 siz[fa][k]siz[p][k1] s i z [ f a ] [ k ] − s i z [ p ] [ k − 1 ] ),时间复杂度仍然是 Onk O ( n k )
代码如下:

#include<bits/stdc++.h>
#define N 100005
using namespace std;
int first[N],n,siz[N][21],cnt=0,dp[N],fa[N],k;
struct Node{int v,next;}e[N<<1];
inline void add(int u,int v){e[++cnt].v=v,e[cnt].next=first[u],first[u]=cnt;}
inline int read(){
    int ans=0;
    char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
    return ans;
}
inline void write(int x){
    if(x>9)write(x/10);
    putchar((x%10)^48);
}
inline void dfs(int p){
    for(int i=first[p];i;i=e[i].next){
        int v=e[i].v;
        if(v==fa[p])continue;
        fa[v]=p;
        dfs(v);
        for(int j=1;j<=k;++j)siz[p][j]+=siz[v][j-1];
    }
}
inline int solve(int p){
    int ret=siz[p][k],pos=k-1;
    while(fa[p]&&pos!=-1){
        ret+=siz[fa[p]][pos]-(pos?siz[p][pos-1]:0);
        --pos,p=fa[p];
    }
    return ret;
}
int main(){
    n=read(),k=read();
    for(int i=1;i<n;++i){
        int u=read(),v=read();
        add(u,v),add(v,u);
    }
    for(int i=1;i<=n;++i)siz[i][0]=read();
    dfs(1);
    for(int i=1;i<=n;++i)for(int j=1;j<=k;++j)siz[i][j]+=siz[i][j-1];
    for(int i=1;i<=n;++i)write(solve(i)),puts("");
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值