P3177 [HAOI2015] 树上染色
P3177 [HAOI2015] 树上染色
大意
一棵 \(n\) 个节点的树,并将其中的 \(k\) 个节点染成黑色,并将其余节点染成白色。收益就是把同色节点之间的两两距离加起来,求这个收益的最大值。
思路
我们发现光是枚举每个点的颜色,时间复杂度就已经爆炸了,所以这个方法肯定不能多看一眼。竟然枚举点不行,那我们可以尝试考虑 \(dp\) 每条边的贡献加起来。一条边将图分成了两部分,假设这条边左侧有 \(b1\) 个小黑子,\(w1\) 个小白子,右侧有 \(b2\) 个小黑子,\(w2\) 个小黑子的话,显然这条边的贡献为 \((b_1 \times b_2 + w_1 \times w_2) \times dist_{u,v}\),其中 \(dist_{u,v}\) 是 \(u\) 到 \(v\) 这条边的长度。所以定义 \(dp_{i,j}\) 表示以 \(i\) 为根的子树里有 \(j\) 个小黑子时这个子树里面的边的总贡献最大是多少。然后进行一个树上背包 \(dp_{u,j} = \max (dp_{u,j-t} + dp_{u,t} + dist_{u,v} \times (t \times (k-t) + (size_v - t) \times (n - k size_v + t)))\) 其中 \(size_i\) 为以 \(i\) 为根的子树大小,\(size_i - t\) 即子树 \(i\) 内小白子的个数。理解这些递归式就可与去愉快的写代码啦。
code:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
#define endl '\n'
#define inf 0x7f7f7f7f7f7f7f7f
const int N=2001;
int n,m,siz[N],dp[N][N];
struct road{
int v,w;
};vector<road> g[N];
void dfs(int u,int fa){
dp[u][0]=dp[u][1]=0;
siz[u]=1;
for(int i = 0;i<g[u].size();++i){
int v=g[u][i].v,w=g[u][i].w;
if(v==fa) continue;
dfs(v,u);
siz[u]+=siz[v];
for(int j = min(m,siz[u]);j>=0;--j){
if(dp[u][j]^-1) dp[u][j]+=dp[v][0]+w*(siz[v]*(n-m-siz[v]));
for(int k = min(j,siz[v]);k;--k){
if(dp[u][j-k]^-1)
dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]+w*(k*(m-k)+(siz[v]-k)*(n-m-siz[v]+k)));
}
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(dp,-1ll,sizeof(dp));
// freopen("","r",stdin);
// freopen("","w",stdout);
//↑以上为初始化 ↑
//------------------------------
cin >> n >> m;
if(n-m<m) m=n-m;
for(int i = 1;i<n;++i){
int u,v,w;
cin >> u >> v >> w;
g[u].push_back({v,w});
g[v].push_back({u,w});
}
dfs(1,-1);
cout << dp[1][m] << endl;
return 0;
}

浙公网安备 33010602011771号