2019 Multi-University Training Contest 2:Harmonious Army(最小割模型)

题目大意:有 n 个人,有两种职业,Mage和Warrior,有m对关系,没对关系有u,v,A,B,C,u,v指的是编号为u,v的人。
若这俩人都为 Mage,则你获得 A 的力量值。若这俩人都为 Warrior ,你会获得 C力量值,若一人为Mage一人为Warrior,你会获得B力量值,现在让你来安排n个人的职业,使得获得的力量值最大。

分析:有 2 n ∗ m 2 ^ n * m 2nm的暴力解法,题目的本意是让你去构造一个两个集合,使得一部分人属于集合A,另一部分属于集合B,最后总力量值最大。
解法:运用网络流建出模型图,每个点与S连一条边,与T连一条边,点间关系边保留。这样的图模型的一个割[S,T],正好将所有的点分成两个集合,也就是一个割对应一个答案,通过网络流算法我们可以求得最小割。通过构图赋予最小割意义可以使得边权贡献扣去最小割得出的是最优解,考虑一对关系x,y的构图:
在这里插入图片描述
割有4种:
1.割掉a,b,代表x,y都在t集合,选的都是t职业
2.割掉c,d,代表x,y都在s集合,选的都是s职业
3.割掉a,e,d,x选t,y选s
4.割掉b,e,c,x选s,y选t
设 s集合 是Warrior,t集合 是 Mage
可以构造方程组:
a + b = A + B
c + d = B + C
a + e + d = A + C
b + e + c = A + C
构造权值的思路无非是想要使得 :扣去割集对应的权值得到的是一个答案。
通过4个式子可以算出每条边的权值:
a = b = A + B 2 , c = d = B + C 2 , e = A + C 2 − B a = b = \frac{A + B}{2},c = d = \frac{B + C}{2},e = \frac{A + C}{2} - B a=b=2A+Bc=d=2B+Ce=2A+CB
每一对关系都可以按这个上面过程构造图,求出最小割,然后用所有边权贡献之和扣去最小割,得到答案。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
const int maxm = 2e5 + 10;
const int inf = 0x3f3f3f3f;
int a[3000],n,m;
struct ss{
	int u,v,nxt;
	double w;
}edg[maxm];
int head[3000],cnt,d[3000];
bool is[maxn];
void init() {
	cnt = 0;
	memset(head,-1,sizeof head);
}
void add(int u,int v,double w) {
	edg[cnt].u = u;
	edg[cnt].v = v;
	edg[cnt].w = w;
	edg[cnt].nxt = head[u];
	head[u] = cnt++;
}
bool bfs(int s,int t) {
	queue<int> q;
	memset(d,0,sizeof d);
	q.push(s);d[s] = 1;
	while(!q.empty()) {
		int top = q.front();
		q.pop();
		for(int i = head[top]; i + 1; i = edg[i].nxt) {
			int v = edg[i].v;
			double w = edg[i].w;
			if(w && !d[v]) {
				d[v] = d[top] + 1;
				q.push(v);
			}
		}
	}
	return d[t] > 0;
}
double dfs(int s,int t,double inflow) {
	if(s == t || !inflow) return inflow;
	double flow = 0;
	for(int i = head[s]; i + 1; i = edg[i].nxt) {
		int v = edg[i].v;
		double w = edg[i].w;
		if(w && d[v] == d[s] + 1) {
			double x = dfs(v,t,min(w,inflow));
			inflow -= x;flow += x;
			edg[i].w -= x;edg[i ^ 1].w += x;
			if(inflow == 0) break;
		}
	}
	if(flow == 0) d[s] = -2;
	return flow;
}
double dinic(int s,int t) {
	double ans = 0;
	while(bfs(s,t)) ans += dfs(s,t,inf);
	return ans;
}
int main() {
	while(~scanf("%d%d",&n,&m)) {
		init();
		int s = 0,t = n + 1;
		int u,v,a,b,c;
		double sum = 0;
		for(int i = 1; i <= m; i++) {
			scanf("%d%d%d%d%d",&u,&v,&a,&b,&c);
			add(s,u,(a + b) / 2.0);
			add(u,s,0);
			add(s,v,(a + b) / 2.0);
			add(v,s,0);
			add(u,v,(a + c) / 2.0 - b);
			add(v,u,0);
			add(v,u,(a + c) / 2.0 - b);
			add(u,v,0);
			add(u,t,(c + b) / 2.0);
			add(t,u,0);
			add(v,t,(c + b) / 2.0);
			add(t,v,0);
			sum += a + b + c;
		}
		sum -= dinic(s,t);
		printf("%.0lf\n",sum);
	}
	return 0;
}

总结:本题用的是最小割模型,类似最大权闭合子图,构出的图的割集[S,T]对应一个解,只要考虑到这一点,就可以从这点出发去构造边权,使得答案 a n s ans ans可以由总贡献 - 最小割 得到。
(网络流真神奇)两种模型的相同点在于:答案都是将点分成两个点集,最大权闭合的答案是s点所在集,最大权闭合子图的选出一个点集V1,整个点就分成了V1和V - V1两个点集。如果将每个点和s,t适当建边构图,通过网络流可以割出两个点集然后去构造得到答案的方式(当然思维不能钉死在这)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值