P3177 [HAOI2015]树上染色 思维+树上背包

点之间距离不好直接求。

可以转化为边的贡献。(感觉是经典思维套路了)

然后就可以转化为经典树形dp题。(舞会那道)

dp[i][j],以i为根的子树,染色j个黑色点,所有边的贡献。

一个边u-v的贡献是:u这边黑点个数*v这边黑点个数*w+u这边白点个数*v这边白点个数*w

然后树上分组背包即可。

这题理论最坏情况下是ON^3的复杂度。

但我们加上if(dp[x][j-k]!=-1)这句话后,

有效执行次数就降到了 On^2 *T  T 是一个小常数。

(虽然循环执行了n^3,但很多是continue,运行很快)

所以网上题解说复杂度On^2我是不认同的。

给你构造一个链,循环次数就是趋向于On^3(不信的话自己造数据跑一跑,只不过有效次数是在On^2,所以这题才能过)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const double PI= acos(-1.0);
const int M = 2000+7;

int head[M],cnt=1;
struct EDGE{int to,nxt,w;}ee[M*2];
void add(int x,int y,int w){ee[++cnt].nxt=head[x],ee[cnt].w=w,ee[cnt].to=y,head[x]=cnt;}

int n,m;
ll dp[M][M];//以i为根的子树,染色j个黑色,其子树包含的边的贡献和
int siz[M];
//每条边的贡献为: (边两边的白点个数相乘+边两边的黑点个数相乘)*边权w 
void dfs(int x,int fa)
{
	siz[x]=1;
	for(int i=head[x];i;i=ee[i].nxt)
	{
		int y=ee[i].to,w=ee[i].w;
		if(y==fa)continue;
		dfs(y,x);
		siz[x]+=siz[y];
	}
	dp[x][0]=dp[x][1]=0;//x点染成黑色或白色,其余均为不合法。
	 
	for(int i=head[x];i;i=ee[i].nxt)
	{
		int y=ee[i].to,w=ee[i].w;
		if(y!=fa)
		for(int j=min(siz[x],m);j>=0;j--)
		{
			for(int k=0;k<=min(siz[y],j);k++)
			{
				//保证棵子树选一个染色方案。
				if(dp[x][j-k]!=-1)
				{
					ll val=(ll)k*(m-k)*w+(ll)(siz[y]-k)*(n-m-siz[y]+k)*w;
					dp[x][j]=max(dp[x][j],dp[x][j-k]+dp[y][k]+val);
				}
				// k , siz[y]-k   m-k , n-m-siz[y]+k
			}
		}
	}
}
int main()
{
  	cin>>n>>m;
  	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
	 } 
	memset(dp,-1,sizeof(dp));
	dfs(1,0);
	//for(int i=0;i<=m;i++)
	cout<<dp[1][m]<<endl;
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值