srm 619 T2

题目大意:给定一棵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;
	}
};


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值