题目大意:给定一棵n个节点的有根树,再给定 m 个任务(第i个任务有个花费costi),每个节点必须选择两个不同的任务。假设第i个节点选择的任务为 fi, gi,则总花费为sigma(costfi + costgi)。一种合法方案要满足的条件为:对于任意的节点i,他和他的所有儿子节点都有办法通过选择两个任务之一,使得节点i和他的所有儿子任务两两不同。求花费最少的合法方案。
主要思路:由于每个节点只影响儿子和父亲,所以很容易想到dp。显然根节点只有一个任务是有用的,而中间节点有其儿子只有一个任务是有用的。考虑fij 为 第 I 个 子树所有节点已经全部处理完毕,且第i个子树只选了任务j。接下来考虑怎么从儿子转移到父亲,由于儿子在父亲范围内不是选择J 就是 另外选一个任务 k。如果儿子选的是任务J(儿子在儿自己范围内选择了 J),则第二任务就没有用了,所以就选择一个可选的价值最低的一个。如果儿子选的是任务K,则儿子的两个任务分别是 J,K。令 w[i][j] 代表第 I 个儿子在父亲回合选择任务 J 的最小花费,这样每个儿子对应恰好一个任务,只需要做一遍二分图最小权匹配就能完成转移了。
Ps:还是第一次见到利用权匹配的转移, 赞。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define gett(x) ((x) == 0) ? 1 : 0
using namespace std;
const int N = 35;
vector<int> son[N];
int f[N][N];
int w[N][N];
int my[N], slack[N];
int lx[N], ly[N];
bool visx[N], visy[N];
bool find(int x, int m) {
visx[x] = 1;
for (int i = 0; i < m; ++i)
if (lx[x] + ly[i] == w[x][i]) {
if (visy[i]) continue;
visy[i] = 1;
if ( my[i] == -1 || find(my[i], m)) {
my[i] = x;
return 1;
}
} else slack[i] = min(slack[i], lx[x] + ly[i] - w[x][i]);
return 0;
}
int minvalue(int n, int m) {
memset(lx, 130, sizeof(int) * n);
memset(ly, 0, sizeof(int) * m);
memset(my, -1, sizeof(my));
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j) {
lx[i] = max(lx[i], w[i][j] = -w[i][j]);
}
for (int i = 0; i < n; ++i)
for (memset(slack, 63, sizeof(slack));;) {
memset(visx, 0, sizeof(visx));
memset(visy, 0, sizeof(visy));
if (find(i, m)) break;
int d = 1 << 30;
for (int j = 0; j < m; ++j)
if (!visy[j]) d = min(d, slack[j]);
for (int j = 0; j < n; ++j)
if (visx[j]) lx[j] -= d;
for (int j = 0; j < m; ++j)
if (visy[j]) ly[j] += d;
else slack[j] -= d;
}
int ans = 0;
for (int i = 0; i < m; ++i)
if (~my[i]) ans += w[my[i]][i];
return -ans;
}
class GoodCompanyDivOne {
public:
int minimumCost(vector <int> superior, vector <int> training) {
sort(training.begin(), training.end());
int m = training.size(), n = superior.size();
for (int i = 1; i < n; ++i) son[superior[i]].push_back(i);
memset(f, 63, sizeof(f));
for (int i = n - 1; i >= 0; --i) {
if (son[i].size() + 1 > m) return -1;
for (int j = 0; j < m; ++j) {
int p = son[i].size();
for (int ileft = 0; ileft < p; ++ileft)
for (int jleft = 0; jleft < m; ++ jleft) {
int &t = w[ileft][jleft];
if (jleft == j) {
w[ileft][jleft] = 1 << 30;
continue;
}
t = f[son[i][ileft]][jleft] + training[gett(jleft)];
for (int k = 0; k < m; ++k)
if (k != jleft)
t = min(t, f[son[i][ileft]][k] + training[jleft]);
}
f[i][j] = minvalue(p, m) + training[j];
}
}
int ans = f[0][0] + training[1];
for (int i = 1; i < m; ++i)
ans = min(ans, f[0][i] + training[0]);
return ans;
}
};