Magic Potion

2018 ICPC 南京站

Problem I. Magic Potion
Input file: standard input
Output file: standard output

There are n heroes and m monsters living in an island. The monsters became very vicious these days,so the heroes decided to diminish the monsters in the island. However, the i-th hero can only kill one monster belonging to the set Mi.Joe, the strategist, has k bottles of magic potion, each of which can buff one hero’s power and let him be able to kill one more monster. Since the potion is very powerful, a hero can only take at most one bottle of potion. Please help Joe find out the maximum number of monsters that can be killed by the heroes if he uses the optimal strategy.

Input
The first line contains three integers n, m, k (1 ≤ n, m, k ≤ 500) — the number of heroes, the number of monsters and the number of bottles of potion. Each of the next n lines contains one integer t i t_i ti, the size of M i M_i Mi, and the following t i t_i ti integers M i M_i Mi, j (1 ≤ j ≤ t i t_i ti), the indices (1-based) of monsters that can be killed by the i-th hero (1 ≤ t i t_i ti ≤ m, 1 ≤ M i M_i Mi ,j ≤ m).

Output
Print the maximum number of monsters that can be killed by the heroes.

Examples(standard input standard output)
Input:
3 5 2
4 1 2 3 5
2 2 5
2 1 2
OutPut:
4

Input:
5 10 2
2 3 10
5 1 3 4 6 10
5 3 4 6 8 9
3 1 9 10
5 1 3 6 7 10
Output:
7

题意
有n个英雄和m只怪物,每个英雄只能击杀特定的几只怪物,每个英雄最多只能击杀一只怪物,有k瓶复活药水,每个英雄最多只能使用一瓶,问:最多可以击杀多少只怪物

思路:
总的来说,就是一道裸的网络最大流的题目。至于要如何想到是用网络流做,我就分享一下自己的经历:首先是看到每个英雄可以杀的怪物是在一个特定集合里的,且只能杀一个,如果没有复活药水,那么显然可以用匈牙利去写一个二分图最大匹配,但是由于复活药水的存在,说明不是二分图但又有点像。再想,既然是要求最多能杀掉多少怪物,且还跟二分图有点类似,自然而然我就想到网络流了,所以就直接写了。

如何建图:

  1. 首先,建立超级源点和汇点,源点向每个英雄都连一条流量为1的边每个怪物都向汇点连一条流量为1的边。对于超级源点,需要限制其流量为 英雄数量+复活药水的数量, 所以可以再建立一个超级源点2,连向之前建的超级源点1,并把这条边的流量设为 英雄数量+复活药水的数量(可能听着有点绕,代码中由详解)
  2. 其次,对于每个英雄,向每一个他可以攻击的怪物都连一条流量为1的边
  3. 然后就是复活药水怎么用了,由于每个英雄最多只能使用一瓶复活药水,那么我们就再建立一个点(中转站),超级源点1向这个点连一条流量为复活药水数量的边,这个点再向每个英雄连一条流量为1的边。这样就可以保证,每个英雄最多只能使用一瓶药水,同时,由于源点1到这个点的流量限制,复活药水的使用数量也得到了控制。
  4. 说说我当时的错误,我以为不需要建立中转站,所以对于每一个英雄,向向每一个他可以攻击的怪物都连了一条流量为2的边,但是,实际上这是把英雄都当成了复活药水,也就是说,有些情况下,他会剥夺某几个英雄唯一的攻击次数给其他英雄以获得更大的结果,这样就必然违背题意了。可能有点模糊,用例子来说明一下。
    如下例:绿色的边代表英雄可以攻击第几个怪物,如果按照我原先的做法,显然获得的答案是3,但正确答案应该是2。
    在这里插入图片描述
    AC代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 10005
#define maxm 1000005
//快读部分
inline int read(void){
    
    
    int k=0; char c=getchar();
    while(c<48||c>57) c=getchar();
    while(c>=48&&c<=57) k=k*10+c-'0',c=getchar();
    return k;
}
//链式前向星存边
struct edge{
    
    
	int to, cap, flow, nxt;
}edg[maxm];
int head[maxn];
int now[maxn];
int cnt;
//加边函数
void addedge(int u, int v, int cap){
    
    
	edg[cnt].to=v, edg[cnt].cap=cap, edg[cnt].nxt=head[u], head[u]=cnt++;
	edg[cnt].to=u, edg[cnt].cap=0, edg[cnt].nxt=head[v], head[v]=cnt++;
}

int n, m, st, en, u, v, w, k;
bool vis[maxn];
int depth[maxn];
//最大流 Dinic 模板
//寻找增广路
bool BFS(){
    
    
	for(int i=0;i<=n+m+3;++i) vis[i]=0, now[i]=head[i];
	queue<int> que; que.push(st);
	depth[st]=0, vis[st]=1;
	while(que.size()){
    
    
		int nw=que.front(); que.pop();
		for(int i=head[nw];i^-1;i=edg[i].nxt){
    
    
			int to=edg[i].to, flow=edg[i].flow, cap=edg[i].cap;
			if(!vis[to] && cap>flow){
    
    
				vis[to]=1;
				depth[to]=depth[nw]+1;
				que.push(to);
			}
		}
	}
	return vis[en];
}
//寻找当前最大可以流的流量
int DFS(int nw, int ha){
    
    
	if(nw==en || !ha) return ha;
	int re=0;
	for(int i=now[nw];ha && i^-1;i=edg[i].nxt){
    
    
		now[nw]=i;
		int to=edg[i].to, f;
		if(depth[nw]+1==depth[to] && (f=DFS(to, min(ha, edg[i].cap-edg[i].flow)))>0){
    
    
			edg[i].flow+=f;
			edg[i^1].flow-=f;
			re+=f;
			ha-=f;
		}
	}
	return re;
}

ll Dinic(){
    
    
	ll re=0;
	while(BFS())
		re+=1ll*DFS(st, 0x3f3f3f3f);
	return re;
}

int main(){
    
    
	std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	memset(head, -1, sizeof(head));
	//变量说明
	//1~m为怪物, m+1~m+n为英雄
	//0为超级源点1, n+m+2为超级源点2, n+m+1为超级汇点, n+m+3为中转站
	//变量有点乱见谅。。。。。
	cin>>n>>m>>k, st=n+m+2, en=n+m+1;//st为超级源点2, en为超级汇点
	addedge(n+m+2, 0, n+k);//超级源点2 连向 超级源点1,容量为 英雄数量+药水数量, 用于限制流量
	addedge(0, n+m+3, k);//超级源点1 向 中转站连一线 流量为k 的边
	for(int i=1;i<=n;++i) addedge(n+m+3, m+i, 1); //中转站向 每一个英雄连一条 流量为1的边
	for(int i=1;i<=m;++i) addedge(i, n+m+1, 1);//每个怪物向汇点连一条 流量为1的边
	for(int i=1;i<=n;++i) addedge(0, m+i, 1);//超级源点1向每一个英雄连一条流量为1的边
	//每个英雄向每一个自己可以攻击的怪物连一条流量为1的边
	for(int i=1;i<=n;++i){
    
    
		int t; cin>>t;
		for(int j=1;j<=t;++j){
    
    
			int x; cin>>x;
			addedge(m+i, x, 1);
		}
	}
	cout<<Dinic();

	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45757639/article/details/111344197