最大流. 注意有可能有多个人拥有同一个门的钥匙, 而且客人是按题目输入的顺序来的.

所以客人间要连边.

丢了个sap模板


代码:

#include <string>
#include <cmath>
#include <queue>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
//调用方法:
//init()初始化,addEdge()增加边,maxFlow()求最大流
#define MAXN 1110 //顶点个数
#define MAXM MAXN*MAXN //边的条数
#define INF 1000000000
struct Edge {
int a, b; //边a->b
int c, f; //容量c,流量f
Edge *next, *back; //下一条边next,反向边back
void setEdge(int a, int b, int c, Edge *next) {
this->a = a;
this->b = b;
this->c = c;
this->next = next;
this->back = NULL;
this->f = 0;
}
} *edge[MAXN], nextEdge[MAXM];
int dist[MAXN]; //距离标号
int edgeNum = 0; //己经使用的边的个数
int counts[MAXN]; //各标号个数,出现断屋即无增广路
void init(int n) {
for(int i = 0; i < n; ++i) {		///0~n-1		??
edge[i] = NULL;
counts[i] = 0;
}
edgeNum = 0;
}
void init_label(int n, int s, int t) {//顶点个数n,源s, 汇t
queue<int> que;
que.push(t);
memset(dist, -1, sizeof(dist));
//for(int i = 0; i < MAXN; ++i) {
// dist[i] = -1;
//}
dist[t] = 0;
++counts[dist[t]];
while(!que.empty()) {
int now = que.front();
que.pop();
for(Edge *next = edge[now]; next != NULL; next = next->next) {
if(next->f != 0) continue;
int b = next->b;
if(dist[b] == -1) {
dist[b] = dist[now] + 1;
++counts[dist[b]];
que.push(b);
}
}
}
}
void addEdge(int x, int y, int c) {//增加一条x->y的弧,容量为c
nextEdge[edgeNum].setEdge(x, y, c, edge[x]);
nextEdge[edgeNum + 1].setEdge(y, x, 0, edge[y]);
edge[x] = &nextEdge[edgeNum];
edge[y] = &nextEdge[edgeNum + 1];
edge[x]->back = edge[y];
edge[y]->back = edge[x];
edgeNum += 2;
}
int maxFlow(int n, int s, int t) {
int ret = 0;
init_label(n, s, t);
Edge *path[MAXN]; //如果MAXN很大,可以开全局数组
Edge *current[MAXN]; //如果MAXN很大,可以开全局数组
memcpy(current, edge, sizeof(edge));
int path_n = 0; //路径长度
int i = s;
while(1) {
if(i == t) { //找到增广路
int minFlow = INF, minK; //最小流和瓶颈位置值
for(int k = 0; k < path_n; ++k) {
if(path[k]->c < minFlow) {
minFlow = path[k]->c;
minK = k;
}
}
ret += minFlow;
for(int k = 0; k < path_n; ++k) {
path[k]->c -= minFlow;
path[k]->back->c += minFlow;
path[k]->f += minFlow;
path[k]->back->f = -(path[k]->f);
}
path_n = minK;
i = path[path_n]->a;
}
if(dist[i] != 0 && counts[dist[i] - 1] == 0) break;
Edge *next;
for(next = current[i]; next != NULL; next = next->next) {
if(next->c == 0) continue;
int y = next->b;
if(dist[i] == dist[y] + 1) {
break;
}
}
if(next != NULL) { //next是一张允许弧
current[i] = next;
path[path_n++] = next;
i = next->b;
} else { //无允许弧,修改标号
int minLabel = n;
for(Edge * next = edge[i]; next != NULL; next = next->next) {
if(next->c == 0) continue;
int y = next->b;
if(dist[y] < minLabel) {
minLabel = dist[y];
current[i] = next; //最小标号就是最新的允许弧
}
}
--counts[dist[i]];
dist[i] = minLabel + 1;
++counts[dist[i]];
if(i != s) { //路径改变,顶点肯定不是允许弧
--path_n;
i = path[path_n]->a;
} else if(dist[i] > n){ //没有增广路
return ret;
}
}
}
return ret;
}

// 顾客在买完猪便将门锁上,而且顾客是按照顺序来的。
int pre[1002];
int main()
{
	int tn, tm;
	while(scanf("%d%d", &tm, &tn)!=EOF)
	{
		memset(pre, -1, sizeof(pre));
		init(tm+tn+2);		
		for(int i=0; i<tm; i++)
		{
			int tt;
			scanf("%d", &tt);
			addEdge(tm+tn, i, tt);
		}
		for(int i=0; i<tn; i++)
		{
			int ta; scanf("%d", &ta);
			while(ta--)
			{
				int tk; scanf("%d", &tk);
				if(pre[tk]==-1)
					addEdge(tk-1, i+tm, INF);
				else
					addEdge(pre[tk], i+tm, INF);
				pre[tk] = i+tm;
			}
			int tb; scanf("%d", &tb);
			addEdge(i+tm, tm+tn+1, tb);
		}
		printf("%d\n", maxFlow(tm+tn+2, tm+tn, tm+tn+1));		//0-th
	}
}