码迷,mamicode.com
首页 > 其他好文 > 详细

换根dp「小奇的仓库·randomwalking·」

时间:2019-09-28 13:01:56      阅读:129      评论:0      收藏:0      [点我收藏+]

标签:event   路径   sub   --   stdout   集中   就是   task   define   

把以前考试换根题集中写一下

随便选一个点做根一遍$dfs$求子树内贡献,再通过特殊手段算$ans[1]$,最后$dfs$求其他$ans$

拆成子树内,子树外分别算贡献差,得儿子是很常见套路了

小奇的仓库

技术图片

 

 $M<=15$

题解

很久之前做的换根dp,当时觉得真是神仙,现在看还是觉得很神仙

不同于一般换根dp,这个题$n^2$并不好写

所以$n^2$算法就省略了

考虑$M$非常小,可以计算$M$对答案影响

一个直接的想法是先算出来原答案,再减去现在答案

            //本来为j现在异或M,变化了j-delta
            //你都按j算的
            //本来j,现在j-1 delta=1
            //所有结果减1

考虑如何算出原答案,对于一个点来说很好算,我们要用一次换根算出来其他点答案

$ans[1]=\sum\limits_{i=2}^{n}dis[i]$

换根

技术图片

 

思考 从x转移到y,那么你子树内点贡献减少edg,子树外点贡献增加edg

那么$ans[y]=ans[x]-sz[y]*edg+(n-sz[y])*edg$

然后考虑算delta

$f[x][i]$表示x子树内路径长mod16后为i条数

转移$f[x][i+edg]=\sum\limits_{y}^{y\in son} f[y][i]$

$g[x][i]$表示整棵树内路径长mod16后为i条数

初始$g[1]=f[1]$

考虑换根

分为几部分贡献,子树内,子树外

子树内很简单$g[y][i]+=f[y][i]$

子树外$g[y][edg+j]+=g[x][j]$即子树外距离当前为$j$的加上当前$edg$即为到当前点$edg+j$的

但这样会算重,$g[x][j]$我们把它当作子树外的了,实际它是整棵树贡献,$g$要减去子树内贡献

$-f[y][j-edg]$即可,子树内要到x距离为$j$那么到$y$距离肯定为$j-edg$

代码

技术图片
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define A 1010101
#define mod 16
ll head[A],ver[A],nxt[A],edg[A],f[A][30],g[A][30],sum[A],dis[A],sz[A],ans[A];
ll tot=1,n,m,M;
void add(ll x,ll y,ll z){
    ver[++tot]=y,nxt[tot]=head[x],head[x]=tot,edg[tot]=z;
}
void dfs(ll x,ll pre){
    sz[x]=1;
    for(ll i=head[x];i;i=nxt[i]){
        ll y=ver[i];
        if(y==pre) continue ;
        dis[y]=dis[x]+edg[i];
        dfs(y,x);
        sz[x]+=sz[y];
    }
}
void dfs0(ll x,ll pre){
    for(ll i=head[x];i;i=nxt[i]){
        ll y=ver[i];
        if(y==pre) continue ;
        ans[y]=ans[x]-(sz[y]*edg[i])+((n-sz[y])*edg[i]);
        dfs0(y,x);
    }
}
void dfs1(ll x,ll pre){
    f[x][0]=1;
    for(ll i=head[x];i;i=nxt[i]){
        ll y=ver[i];
        if(y==pre) continue ;
        dfs1(y,x);
        for(ll j=0;j<=15;j++)
            f[x][(j+edg[i])%mod]+=f[y][j];
    }
}
void dfs2(ll x,ll pre){
    for(ll i=head[x];i;i=nxt[i]){
        ll y=ver[i];
        if(y==pre) continue ;
        for(ll j=0;j<=15;j++)
            g[y][(j+edg[i])%mod]+=f[y][(j+edg[i])%mod]+(g[x][j]-f[y][(j-edg[i]%mod+mod)%mod]);//
        dfs2(y,x);
    }
}
int main(){
//    freopen("da.in","r",stdin);freopen("ans.bf","w",stdout);
    scanf("%lld%lld",&n,&M);
    for(ll i=1,a,b,c;i<=n-1;i++){
        scanf("%lld%lld%lld",&a,&b,&c);
        add(a,b,c);add(b,a,c);
    }
    dis[0]=0;
    dfs(1,0);
    for(ll i=2;i<=n;i++)
        ans[1]+=dis[i];
    dfs0(1,0);
    dfs1(1,0);
    for(ll i=0;i<=15;i++)
        g[1][i]=f[1][i];
    dfs2(1,0);
    for(ll i=1;i<=n;i++){
        g[i][0]--;
        for(ll j=0;j<=15;j++){
            ll delta;
            delta=(j-(j^M));
            //本来为j现在异或M,变化了j-delta
            //你都按j算的
            //本来j,现在j-1 delta=1
            //所有结果减1
            ans[i]-=delta*g[i][j];
//            printf("g[%lld][%lld]=%lld\n",i,j,g[i][j]);
        }
        printf("%lld\n",ans[i]);
    }
}
View Code

randomwalking

技术图片

 

题解

$n^2$很简单,考虑换根

$f[x][0]$表示子树内走到当前期望

随便选一个做根

对于非根节点:$f[x][0]=a[x]+\sum\limits_{y}^{y\in son[x]} \frac{1}{deg[x]-1} f[y][0]$

对于根:$f[x][0]=a[x]+\sum\limits_{y}^{y\in son[x]} \frac{1}{deg[x]} f[y][0]$

$f[x][1]$表示整棵树走到当前期望

$f[1][1]=f[1][0]$

考虑换根

技术图片

仍分为子树内子树外

子树内贡献就是$(f[y][0]-a[y])*(deg[y]-1)$

子树外看似很难求但还是可求的

看从$x$转移到$y$

$(f[x][1]-a[x])*deg$求出$y$子树和别的子树贡献

$(f[x][1]-a[x])*deg-f[y][0]$就是子树外的

还有一个注意点,本来$x$为根现在$y$为根了,$x$本来出度为$2$现在变为了$1$deg也要变化

$f[y][1]=(\frac{(f[x][1]-a[x])*deg[x]-f[y][0])}{(deg[x]-1)}+a[x]+(f[y][0]-a[y])*(deg[y]-1))}{deg[y]}+a[y];$

代码

技术图片
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define A 2222222
ll sz[A],a[A],head[A],nxt[A],ver[A];
ll n,tot,id;
double deg[A],f[A][2];
void add(ll x,ll y){
    nxt[++tot]=head[x],head[x]=tot,ver[tot]=y;
}
void dpfs(ll x,ll pre){
    for(ll i=head[x];i;i=nxt[i]){
        ll y=ver[i];
        if(y==pre) continue ;
        dpfs(y,x);
        if(pre==0) f[x][0]+=f[y][0]*1/(deg[x]);
        else f[x][0]+=f[y][0]*1/(deg[x]-1);    
    }
    f[x][0]+=a[x];
//    printf("f[%lld]=%.3lf\n",x,f[x][0]);
}
void dpfs2(ll x,ll pre){
    for(ll i=head[x];i;i=nxt[i]){
        ll y=ver[i];
        if(y==pre) continue ;
        double tmp1=(f[x][1]-a[x])*deg[x];
        double tmp2=tmp1-f[y][0];
        double tmp3=(f[y][0]-a[y])*(deg[y]-1);
        if(deg[x]>1)
            f[y][1]=(tmp2/(deg[x]-1)+a[x]+tmp3)/deg[y]+a[y];
        else f[y][1]=(a[x]+tmp3)/deg[y]+a[y];
//        printf("x=%lld tmp1=%.3lf f[%lld]=%.3lf deg=%.3lf tmp2=%.3lf tmp3=%.3lf f[%lld][1]=%.3lf\n",x,tmp1,x,f[x][1]-a[x],deg[x],tmp2,tmp3,y,f[y][1]);
    }
    for(ll i=head[x];i;i=nxt[i]){
        ll y=ver[i];
        if(y==pre) continue ;
        dpfs2(y,x);
    }
}
void sub_task1(){
    dpfs(1,0);f[1][1]=f[1][0];
    dpfs2(1,0);
    id=1;
    for(ll i=1;i<=n;i++)
        if(f[i][1]<f[id][1]) id=i;
    printf("%lld\n",id);
}
int main(){
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    for(ll i=1,c,b;i<n;i++){
        scanf("%lld%lld",&b,&c);
        add(b,c);add(c,b);
        deg[b]++,deg[c]++;
    }
    sub_task1();
}
View Code

 

换根dp「小奇的仓库·randomwalking·」

标签:event   路径   sub   --   stdout   集中   就是   task   define   

原文地址:https://www.cnblogs.com/znsbc-13/p/11602467.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!