zoukankan      html  css  js  c++  java
  • [HAOI2015]树上染色

    [HAOI2015]树上染色

    一.前言

    ​ 呜啦啦啦啦啦啦啦啦整了半天终于勉勉强强的整出来啦!

    好像是省选?反正链接

    二.思路

    ​ 题目是要求很多个链上的权值和,那么如果能断链成边,知道每一条边的贡献次数,是不是会好一点?对于一条边,当且仅当它的两边有这颜色相同的点,它才做出一次贡献。,那么如果分别知道边的两端的黑点个数与白点个数,一手乘法原理算得出来。其实,只需要一边黑点个数就够了。

    设一个点 x 的子树大小为 size(x) ,子树内黑点个数为 y

    则有子树内白点个数为 size(x)-y

    子树以外(就是边的另一端)所有的黑点为 k(总黑点个数)-y ,白点为 n(总点数)-size(x)-k+y

    故一条边的贡献为(dis*(y*(k-y)+(size(x)-y)*(n-size(x)-k+y)))

    接下来,开始我们的 dp

    首先采用从下往上抉择,很像背包问题。只不过一个节点看做一个背包,里面装的是它儿子节点所代表的背包(好像叫做泛化物品的并),其实有点像拿它的子树做01背包的样子。01背包知道吧,就是那个 (f[x]+=f[x-j];) 的玄妙东西,是倒着转移的(顺着就叫无限背包啦),那么比较相似的,令 (f[x][j]) 为在 x 的子树下选了 j 的黑点的答案,则亦有转移方程。

    [f[x][j]=f[x][j-p]+f[v][p]+val\ val=dis*(p*(k-p)+(size(v)-p)*(n-size(v)-k+p)) ]

    (val 是照搬上面的)

    然后第二层注意一样要和01背包一样倒着转移。注意循环的起始一定要合法,不能贸然用 k 开刀。然后最后一层循环我写了两种方法(其中一种注释掉了),两种都是要先将子树纯白的情况计算进去。因为这题有点特殊……(具体原因转luogu题解吧……

    最后是边界条件,不管是哪个子树,只选他自己和不选都是合法的,且贡献为0,所以 (f[x][0]=f[x][1]=0;)

    三.CODE

    const int MAXN=2005;
    int n,k;
    int head[MAXN],ne[2*MAXN],to[2*MAXN],dis[2*MAXN],tot;
    void add(int u,int v,int d){
    	to[++tot]=v;
    	ne[tot]=head[u];
    	head[u]=tot;
    	dis[tot]=d;
    }
    int size[MAXN];
    long long f[MAXN][MAXN];
    int dfs(int x,int fa){
    	size[x]=1;
    	f[x][0]=f[x][1]=0;
    	for(int i=head[x];i;i=ne[i]){
    		int v=to[i];
    		if(v==fa)continue;
    		size[x]+=dfs(v,x);
    		for(int j=min(k,size[x]);j>=0;--j){
    			/*if(f[x][j]!=-1)
    			f[x][j]+=f[v][0]+size[v]*(n-k-size[v])*dis[i];
    			for(int p=min(j,size[v]);p;--p){
    				if(f[x][j-p]==-1)continue;
    				long long val=1LL*(p*(k-p)+(n-k-size[v]+p)*(size[v]-p))*dis[i];
    				f[x][j]=max(f[x][j],f[x][j-p]+f[v][p]+val);
    			}*/
    			for(int p=0;p<=min(j,size[v]);++p){
    				if(f[x][j-p]==-1)continue;
    				long long val=1LL*(p*(k-p)+(n-k-size[v]+p)*(size[v]-p))*dis[i];
    				f[x][j]=max(f[x][j],f[x][j-p]+f[v][p]+val);
    			}
    		}
    	}
    	return size[x];
    }
    int main(){
    	memset(f,-1,sizeof(f));
    	n=read();k=read();
    	for(int i=1,u,v,d;i<n;++i){
    		u=read();v=read();d=read();
    		add(u,v,d);add(v,u,d);
    	}
    	dfs(1,0);
    	printf("%lld",f[1][k]);
    	return 0;
    }
    

    四.后记

    ​ 就在写完后不就有dalao甩出来一个更高级的做法,速度会快很多。用的不是减而是加,好像可以严格证明(O(n^2)),暂时不是很懂,但是快就完了,dfs放一放。实际上用减的话会产生很多不必要且无效的访问,即上文中

    if(f[x][j-p]==-1)continue;一行,但是总的 j+p 上界依旧为 size[x]+size[v] ,所以将 size[x] 的更新放到后面。

    void dfs(int x,int fa){
    	size[x]=1;
    	f[x][0]=f[x][1]=0;
    	for(int i=head[x];i;i=ne[i]){
    		int v=to[i];
    		if(v==fa)continue;
    		dfs(v,x);
    		for(int j=size[x];j>=0;--j){
    			for(int p=size[v];p>=0;--p){
    				long long val=1LL*(p*(k-p)+(n-k-size[v]+p)*(size[v]-p))*dis[i];
    				f[x][j+p]=max(f[x][j+p],f[x][j]+f[v][p]+val);
    			}
    		}
    		size[x]+=size[v];
    	}
    }
    
  • 相关阅读:
    【习题 8-9 1613】 K-Graph Oddity
    【习题 8-8 UVA
    【Hello 2018 D】Too Easy Problems
    【Hello 2018 C】Party Lemonade
    【Hello 2018 B】Christmas Spruce
    【Hello 2018 A】 Modular Exponentiation
    【习题 8-7 UVA
    【习题 8-6 UVA
    【习题 8-4 UVA
    【习题 8-3 UVA
  • 原文地址:https://www.cnblogs.com/clockwhite/p/13364439.html
Copyright © 2011-2022 走看看