[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];
	}
}