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

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

随便选一个点做根一遍$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]);
    }
}

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();
}

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

时间: 2024-08-28 17:25:36

换根dp「小奇的仓库·randomwalking·」的相关文章

换根DP

换根dp的通法:1.第一次扫描时,任选一个点为根,在"有根树"上执行一次树形DP,也就在回溯时发生的,自底向上的状态转移. 2.第二次扫描时,从刚才选出的根出发,对整棵树执行一次dfs,在每次递归前进行自上向下的推导,计算出换根后的解. 1.POJ3585 Accumulation Degree dp[i]以i为根的子树中,把i作为源点的最大流量 转移\(dp[x]=\sum_{y\epsilon son(x)}^{}\left\{\begin{matrix} min(dp[y],le

poj3585 Accumulation Degree(换根dp)

传送门 换根dp板子题(板子型选手 题意: 一棵树确定源点和汇点找到最大的流量(拿出一整套最大瘤板子orz const int maxn=2e5+10; int head[maxn],tot; struct node { int nt,to;long long w; }q[2*maxn]; long long dp[maxn];int cnt[maxn]; void insert(int u,int v,long long w) { q[tot].nt=head[u];q[tot].w=w;q[

小奇的仓库

题目来源:hzwer [题目背景] 小奇采的矿实在太多了,它准备在喵星系建个矿石仓库.令它无语的是,喵星系的货运飞船引擎还停留在上元时代! [问题描述] 喵星系有n个星球,星球以及星球间的航线形成一棵树. 从星球a到星球b要花费[dis(a,b) Xor M]秒.(dis(a,b)表示ab间的航线长度,Xor为位运算中的异或) 为了给仓库选址,小奇想知道,星球i(1<=i<=n)到其它所有星球花费的时间之和. [输入格式] 第一行包含两个正整数n,M.接下来n-1行,每行3个正整数a,b,c,

【换根dp】9.22小偷

换根都不会了 题目大意 给定一棵$n$个点的树和树上一撮关键点,求到所有$m$个关键点距离的最大值$dis_{max}\le LIM$的点的个数. $n,m\le 30000,LIM\le 30000$ 题目分析 考虑在求出一个点的情况下如何转移到其子节点. 对点$u$最直接关心的状态是$mx[u]$:所有关键点到$u$的最大距离. 对点$u$的子节点$v$来说,$u$能带给它的只是“外面的世界”——$v$子树的补集这块贡献,也就是对于$u$的除了$v$子树的$mx[u]$. 因为$mx[u]$

codeforces1156D 0-1-Tree 换根dp

题目传送门 题意: 给定一棵n个点的边权为0或1的树,一条合法的路径(x,y)(x≠y)满足,从x走到y,一旦经过边权为1的边,就不能再经过边权为0的边,求有多少边满足条件? 思路:设$f[u]$为以1为根,自下而上到$u$的末节点是1的合法路径数量,$g[u]$代表以1为根,自下而上到$v$末节点是0的合法路径数量,这个可以通过一遍dfs简单求解. 再设$nf[u]$和$ng[u]$代表以u为根的两种合法路径数量,进行换根dfs,在换根的过程中: 若某一条边是0边,则: $ng[st.to]=

HDU 2196 Computer 二次扫描与换根DP

题意:给定一棵树,求树上所有点到其最远点的距离. 数据范围: 1 <= N <= 100000 ------------------------------------------我是分割线------------------------------------------ 题解:对于每个节点u来说,其可能到达的最长距离为max{其子树内的最长距离,其父节点不经过u的子树内的最长距离}.于是,我们便可以在第一遍dfs中预处理节点x到其子树内的最长距离,顺带求一下次长距离,方便转移. // f[

newcoder 79F 小H和圣诞树 换根 DP + 根号分治

Code: #include <cstdio> #include <algorithm> #include <vector> #include <cmath> #include <map> #define N 100003 #define ll long long #define setIO(s) freopen(s".in", "r" , stdin) , freopen(s".out"

CodeForce - 1187 E. Tree Painting (换根dp)

You are given a tree (an undirected connected acyclic graph) consisting of nn vertices. You are playing a game on this tree. Initially all vertices are white. On the first turn of the game you choose one vertex and paint it black. Then on each turn y

换根dp+暴力+预处理+记忆化搜索——cf1292C好题!

/** 给定一棵树,要求给树边赋值[0,n-2],每个值只能使用一次 S = mex(u,v), mex(u,v)是u-v路径上没有出现过的编号最小的值 问使得S最大的赋值方式 由于很难直接统计答案,所以考虑统计每条边的贡献 包含(0)路径的贡献tot1是其左右子树size的乘积 包含(0,1)的路径的贡献tot2是其左右子树的size乘积 ...依次类推 显然:只包含(1,2)这样的路径是没有贡献的 那么原问题转化为如何分配[0,n-2],使得最后的乘积和最大 dp[u][v]表示路径(u,v