[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 的黑点的答案,则亦有转移方程。
(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];
}
}