P4362 [NOI2002] 贪吃的九头龙
题意
给定 \(n\) 个点的树,有边权。将 \(n\) 个点染成 \(m\) 种颜色,其中 \(1\) 号颜色必须染色 \(k\) 个结点,且 \(1\) 号结点必须染成 \(1\) 号颜色。剩下的 \(m - 1\) 中颜色分别至少出现 \(1\) 次。
一条边的两个结点染色相同的时候,贡献边权,否则贡献为 \(0\);
问:染色达到要求的最小边权和。
思路
首先,因为点 1 要然成一种颜色且要染 \(k\) 个点,所以还有 \(m - 1\) 种颜色,\(n - k\) 个点要染色,如果 \(n - k < m - 1\),那么一定不能染色成功,输出 \(-1\)。
设 \(f_{i, j, k}\) 表示到点 \(i\),染了和点 1 的颜色一样的点的颜色个数,这个点是否染和点 1 颜色一样的颜色个数可以获得最小贡献。
如果 \(m > 2\),这时候,如果都不染和点 1 一样的颜色,颜色不会重合,第一行式子中不用 + w:
\(f_{u, j, 0} = \min(f_{u, j, 0}, f_{u, j - k, 0} + f_{to, k, 0}, f_{u, j - k, 0} + f_{to, k, 1})\)
\(f_{u, j, 1} = \min(f_{u, j, 1}, f_{u, j - k, 1} + f_{to, k, 1} + w, f_{u, j - k, 1} + f_{to, k, 0})\)
如果 \(m = 2\),如果都不染和点 1 一样的颜色,颜色会重合:
\(f_{u, j, 0} = \min(f_{u, j, 0}, f_{u, j - k, 0} + f_{to, k, 0} + w, f_{u, j - k, 0} + f_{to, k, 1})\)
\(f_{u, j, 1} = \min(f_{u, j, 1}, f_{u, j - k, 1} + f_{to, k, 1} + w, f_{u, j - k, 1} + f_{to, k, 0})\)
(仔细对比一下)
然后就是一定要用临时数组,请看我发的帖子。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 310;
struct edge {
int to, next, w;
} e[N * 2];
int head[N], idx = 1;
void add(int u, int v, int w) {
idx++;
e[idx].to = v;
e[idx].next = head[u];
e[idx].w = w;
head[u] = idx;
}
int n, m, a;
int f[N][N][2], g[N][2], sz[N];
void dfs(int u, int fa) {
sz[u] = 1;
f[u][1][1] = f[u][0][0] = 0;
for (int i = head[u]; i; i = e[i].next) {
int to = e[i].to;
if (to == fa) continue;
dfs(to, u);
sz[u] += sz[to];
memset(g, 0x3f, sizeof(g));
for (int j = 0; j <= min(a, sz[u]); j++) {
for (int k = 0; k <= min(j, sz[to]); k++) {
g[j][0] = min(g[j][0], min(f[u][j - k][0] + f[to][k][0] + (m == 2) * e[i].w, f[u][j - k][0] + f[to][k][1]));
g[j][1] = min(g[j][1], min(f[u][j - k][1] + f[to][k][1] + e[i].w, f[u][j - k][1] + f[to][k][0]));
}
}
memcpy(f[u], g, sizeof(g));
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> a;
if (n - a < m - 1) {
cout << "-1\n";
return 0;
}
for (int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
memset(f, 0x3f, sizeof(f));
dfs(1, 0);
cout << f[1][a][1] << '\n';
return 0;
}