zoukankan      html  css  js  c++  java
  • 小奇的仓库「换根DP」

    小奇的仓库「换根DP」

    题目描述

    喵星系有n个星球,星球以及星球间的航线形成一棵树。

    从星球a到星球b要花费 (dis(a,b)) (Xor) (M)
    秒。

    为了给仓库选址,小奇想知道,星球 (i(1<=i<=n)) 到其它所有星球花费的时间之和。

    输入格式

    第一行包含两个正整数 (n,M)。 接下来 (n-1) 行,每行 (3) 个正整数 (a,b,c),表示 (a,b) 之间的航线长度为 (c)

    输出格式

    (n) 行,每行一个整数,表示星球 (i) 到其它所有星球花费的时间之和。

    样例

    样例输入

    4 0
    1 2 1
    1 3 2
    1 4 3
    

    样例输出

    6
    8
    10
    12
    

    数据范围

    对于所有数据,(n <= 100000,M <= 15)


    思路分析

    刷不动题了来水一篇题解不让自己闲着

    • 暴力很暴力,以每个点为根来跑了一遍 (BFS) 水了 (30pts)
    • 然而在上述过程中其实两个点之间的距离是会被重复计算的,而且每次都是重新出发,以前得到的信息都没有用到,这是暴力需要优化的根本所在
    • 每个节点都要作为根,自然就需要用到换根 (DP) ,换根 (DP) 之所以优秀,是因为既可以对所有节点为根进行一系列统计,又不会每次重新开始,而是从上一个进行转移
    • 首先以 (1) 作为根节点,这个距离直接求出来就行了,得出 (ans[1] = sum_{i=2}^{n}dis[i])
    • 接着就是换根 (DP) 的灵魂操作:
      • 假设我们已经处理出以 (x) 为根节点的信息,接下来我们要处理与 (x) 相连的 (y) 节点作为根节点(均指整棵树)。
      • 我们从 (x) 通过相连的边到达 (y) ,那么变化的根本在于这条边的权值上。
      • 我们在经过这条边时,接近了(y) 子树内的节点,而远离了 (y) 子树外的节点,所以以 (y) 为根节点的距离总和可以直接从 (x) 转移
      • 由此得出公式,即 (ans[y] = ans[x]-siz[y]*e[i].dis + (n-siz[y])*e[i].dis) ((siz) 为子树的节点个数)
    • 然后就是这题的另一个核心——异或
      • 考虑 (M) 会对答案产生什么影响,看一眼数据范围,(M) 最大才 (15),那么每个距离的波动范围最大也就是 (15)
      • 我们将处理出的每一个距离 (mod) (16),这样就可以在异或 (M) 时计算出 (M) 对答案产生的影响
      • 如上,我们开两个数组来记录边, (f[x][i]) 表示 (x) 子树内路径长 (mod) (16) 后为 (i) 的边数,而(g[x][i]) 表示 (x) 作为根节点时整棵树的
      • 接下来就是转移,同 (ans) 数组的转移类似,(f[x][(i+e[i].dis)\%16] = f[y][i]),关键是 (g[y][i]) 的转移,对于子树内,直接从 (f) 数组转移即可,而对于子树外,(g[y][(i+e[i].dis)%16]) 如果直接 (+g[x][i]) 会包括 (y) 子树内的点,但当 (y) 作为根时就不满足了,所以要减去 (f[y][i-(e[i].dis)\%16])

    详见代码

    Code

    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #define int long long
    #define N 100010
    using namespace std;
    inline int read(){
    	int x=0,f=1;
    	char ch=getchar();
    	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    	return x*f;
    }
    const int mod = 16;
    int n,m,dis[N],f[N][25],g[N][25],siz[N],head[N],ans[N];
    struct edge{
    	int to,next,dis;
    }e[N<<1];
    int len;
    void addedge(int u,int v,int w){
    	e[++len].to = v;
    	e[len].dis = w;
    	e[len].next = head[u];
    	head[u] = len;
    }
    void dfs(int u,int fa){ //预处理出子树节点个数
    	siz[u] = 1;
    	for(int i = head[u];i;i = e[i].next){
    		int v = e[i].to;
    		if(v==fa)continue;
    		dis[v] = dis[u]+e[i].dis;
    		dfs(v,u);
    		siz[u] += siz[v];
    	}
    	return;
    }
    void get_ans(int u,int fa){ //算出不异或时的结果
    	for(int i = head[u];i;i=e[i].next){
    		int v = e[i].to;
    		if(v==fa)continue;
    		ans[v] = ans[u]-(siz[v]*e[i].dis)+(n-siz[v])*e[i].dis;
    		get_ans(v,u);
    	}
    	return;
    }
    void get_f(int u,int fa){ //计算f
    	f[u][0] = 1;
    	for(int i = head[u];i;i = e[i].next){
    		int v = e[i].to;
    		if(v==fa)continue;
    		get_f(v,u);
    		for(int j = 0;j <= 15;j++){
    			f[u][(j+e[i].dis)%mod] += f[v][j];
    		}
    	}
    	return;
    }
    void get_g(int u,int fa){ //转移g
    	for(int i = head[u];i;i = e[i].next){
    		int v = e[i].to;
    		if(v==fa)continue;
    		for(int j = 0;j <= 15;j++){
    			g[v][(j+e[i].dis)%mod] += f[v][(j+e[i].dis)%mod]+g[u][j]-f[v][(j-e[i].dis%mod+mod)%mod]; //注意这里直接算肯定会出现负数
    		}
    		get_g(v,u);
    	}
    	return;
    }
    
    signed main(){
    	n = read(),m = read();
    	for(int i = 1;i < n;i++){
    		int a,b,c;a = read(),b = read(),c = read();
    		addedge(a,b,c);addedge(b,a,c);
    	}
    	dis[0] = 0;
    	dfs(1,0);
    	for(int i = 2;i <= n;i++)ans[1] += dis[i]; //因为1是最初的根节点,所以我们直接先算出来
    	get_ans(1,0);
    	get_f(1,0);
    	for(int i = 0;i <= 15;i++)g[1][i] = f[1][i];//同理
    	get_g(1,0);
    	for(int i = 1;i <= n;i++){
    		g[i][0]--;
    		for(int j = 0;j <= 15;j++){
    			int delta = (j-(j^m)); //j^m为变化后的数,而j-(j^m)则为变化的大小
    			ans[i] -= delta*g[i][j]; //相应的边减去(也可能为加)变化的贡献
    		}
    		printf("%lld
    ",ans[i]);
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    UVa10820 Send a Table[欧拉函数]
    HDU2929 Bigger is Better[DP 打印方案 !]
    UVa11549计算器谜题[floyd判圈]
    洛谷U4807抽水机[最小生成树]
    CF149D. Coloring Brackets[区间DP !]
    POJ1651Multiplication Puzzle[区间DP]
    POJ2955Brackets[区间DP]
    POJ1129Channel Allocation[迭代加深搜索 四色定理]
    codevs1004四子连棋[BFS 哈希]
    codevs哈希水题
  • 原文地址:https://www.cnblogs.com/hhhhalo/p/13493205.html
Copyright © 2011-2022 走看看