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

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

#include
#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;iint 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;
}

你可能感兴趣的:(#,状态转移,#,树形dp)