题意简述
给定一个有nnn个节点的树,n<=5e5n<=5e5n<=5e5。每个点只能染精确的kkk种颜色,有无限种颜色珂供选择,但是每种颜色不能出现超过两次。如果一条边连接的两个点的两个颜色中有至少一个共同的,这条边就会产生它边权的权值。合理分配使权值最大,输出最大的权值。
思路框架
设dp[i][0/1]dp[i][0/1]dp[i][0/1]表示以iii为根,是/否选择iii到iii的父亲那一条边,的最大权值和。转移即珂。
具体思路
分两个部分。
Part 1. 状态
那么我们为什么这么设状态呢。。。
套路
原因是这样的,我们一开始肯定能想到设前面的dp[i]dp[i]dp[i]表示以iii为根的答案。但是,如果只有这个,我们很难判断一个儿子是否能连上来,因为要连上来就需要有一个颜色与父亲对应,而和儿子只能用k−1k-1k−1个颜色,这状态就不对,或者说,我们少一个状态。
所以我们考虑设dp[i][0/1]dp[i][0/1]dp[i][0/1],加上后面那一维。
Part 2. 转移
首先,把所有儿子的dp[son][1]dp[son][1]dp[son][1]加起来显然是一种选择。记这个默认值为SSS。
然后我们发现,还珂能有儿子连到父亲的边的情况。这种情况,就是dp[son][0]+wdp[son][0]+wdp[son][0]+w,www是这条边的边权。如果这种情况的确更好,我们要把sonsonson这个儿子的默认值dp[son][1]dp[son][1]dp[son][1]换成dp[son][0]+wdp[son][0]+wdp[son][0]+w,对SSS的影响很显然就是S=S−dp[son][1]+dp[son][0]+wS=S-dp[son][1]+dp[son][0]+wS=S−dp[son][1]+dp[son][0]+w。但是我们能接到儿子的边最多就只有kkk条,因为我们现在考虑的uuu点只能染kkk种颜色。
那咋整嘛。也好办,我们把后半部分dp[son][0]+w−dp[son][1]dp[son][0]+w-dp[son][1]dp[son][0]+w−dp[son][1]记下来,从大到小排个序。如果是dp[i][1]dp[i][1]dp[i][1],选前面kkk个即珂;但是dp[i][0]dp[i][0]dp[i][0]只能选前面k−1k-1k−1个,因为还有留一个给父亲。当然,如果这个值是负的,说明选了之后不会更优。一个是负的,后面肯定都是负的了,于是直接 breakbreakbreak掉,不管了。
然后就结束了
代码
#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
#define N 1666666
#define lovelive long long
#define F(i,l,r) for(int i=l;i<=r;++i)
#define D(i,r,l) for(int i=r;i>=l;--i)
#define Fs(i,l,r,c) for(int i=l;i<=r;c)
#define Ds(i,r,l,c) for(int i=r;i>=l;c)
#define Tra(i,u) for(int i=G.Start(u),__v=G.To(i);~i;i=G.Next(i),__v=G.To(i))
#define MEM(x,a) memset(x,a,sizeof(x))
#define FK(x) MEM(x,0)
class Graph
{
public:
int head[N];
int EdgeCount;
struct Edge
{
int To,Label,Next;
}Ed[N<<1];
void clear(int _len)
{
memset(Ed,-1,sizeof(Edge)*(_len+10));
memset(head,-1,sizeof(int)*(_len+10));
EdgeCount=-1;
}
void AddEdge(int u,int v,int w=1)
{
Ed[++EdgeCount]=(Edge){v,w,head[u]};
head[u]=EdgeCount;
}
void Add2(int u,int v,int w=1) {AddEdge(u,v,w);AddEdge(v,u,w);}
int Start(int u) {return head[u];}
int To(int u){return Ed[u].To;}
int Label(int u){return Ed[u].Label;}
int Next(int u){return Ed[u].Next;}
}G;
int n,k;
void R1(int &x)
{
x=0;char c=getchar();int f=1;
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=(f==1)?x:-x;
}
void Input()
{
R1(n),R1(k);
G.clear(n);
F(i,1,n-1)
{
int u,v,w;R1(u),R1(v),R1(w);
G.Add2(u,v,w);
}
}
lovelive dp[N][2];
//0: choose father
//1: don't choose father
void DFS(int u,int f)
{
lovelive sum=0;
vector<lovelive> son;son.clear();
Tra(i,u)
{int v=__v;
if (v!=f)
{
DFS(v,u);
int d=dp[v][0]+G.Label(i)-dp[v][1];
sum+=dp[v][1];
son.push_back(d);
}
}
dp[u][0]=dp[u][1]=sum;
sort(son.begin(),son.end(),greater<lovelive>());
F(i,0,min(k-1,(int)son.size()-1))
{
int v=son[i];
if (v>0)
{
dp[u][1]+=v;
if (i<k-1) dp[u][0]+=v;
}
}
}
void Soviet()
{
F(i,1,n) dp[i][0]=dp[i][1]=0;
DFS(1,1);
printf("%I64d\n",dp[1][1]);
}
#define Flan void
Flan IsMyWife()
{
int t;R1(t);
while(t--)
{
Input();
Soviet();
}
}
}
int main()
{
Flandre_Scarlet::IsMyWife();
getchar();getchar();
return 0;
}
这篇博客介绍了Codeforces 1240C问题的解决方案,涉及一棵最多50万节点的树,每个节点限制使用k种颜色,且颜色不能超过两次。内容围绕如何最大化边权值展开,通过状态dp[i][0/1]表示以i为根节点,是否选择与父节点相连的颜色进行讨论,并详细解析了状态设置的原因和转移方程,最后给出了代码实现。
263

被折叠的 条评论
为什么被折叠?



