托米去购物(最大流)
题目描述:
链接:https://ac.nowcoder.com/acm/problem/16408
来源:牛客网
题目描述
此时的托米老师已经出任CEO,迎娶白富美,走向了人生巅峰!于是这个暑假,托米老师打算在北京一个偏僻的小农村里度过他的假期。
由于这里什么都没有,于是他去超市选了很多生活用品,更多的是吃的,然后推着堆满零食的购物车到柜台等待结账。
当然,我们都知道他的钱包里有很多钱。但是,作为一名为生活精打细算的男孩子,他更愿意使用其他支付方式如:饭券,礼券,不同类型的优惠券等。但是饭券只能用于购买食物,而礼券通常只限于某种类型的礼物。
现在给你托米购物车中物品的数量N和每件物品的价格。也会给出他钱包中的代金券数量M以及允许使用的信息 。在为他的购物付款时,托米可能使用代金券的金额超过他所购物品的成本。也可以在多张代金券之间拆分商品的成本,并使用代金券支付多件商品。
请你计算托米需要为购物支付的额外现金的最小金额。
输入描述:
输入的第一行包含一个整数T,用于指定测试用例的数量。
每个测试用例前面都有一个空白行。
每个测试用例从包含两个正整数N(物品数量)和M(券数量)的行开始。
接下来一行包含N个数字,第i个数字表示托米购物车里第i件物品的价格。
接下来一行包含M个数字,第i个数字表示第i张券的金额。
接下来有M行,当中的第 i 行描述第 i 张卷可以买哪些商品。每行的第一个数字是 K,代表第 i 张卷可以为 K 件商品付款,接下来还有 K 个数,是这 K 件商品的编号
输出描述:
对于每个测试用例输出数字,表示托米需要支付多少现金。
思路:做的第一道网络流问题,感受就是,算法不难,主要还看建图,这道题我们把优惠券也看成物品,在优惠券之后连上可以用来消费的物品,
流量就是物品费用,然后在用一个源点s连上所有优惠券,流量是优惠券的价值,再把物品都连到一个汇点,流量也是物品费用
大概样子就是这样:
S,E分别是源点和汇点,D,E为优惠券,ABC是物品,然后求S到E的最大流(dinc或者ISAP都可以)就是用优惠券能节约的钱,然后用物品总价值减去它就是需要花费的钱
AC代码:
#include<bits/stdc++.h> using namespace std; const int maxn = 3000; const int maxm = 100005; typedef long long ll; const ll inf = 1e15; struct edge { int t, nxt; ll wight; }e[maxm]; int hd[maxn], tot; void add(int f, int t, ll w) { e[++tot] = { t,hd[f],w }; hd[f] = tot; e[++tot] = { f, hd[t],0 }; hd[t] = tot; } int cur[maxn], dep[maxn]; bool bfs(int s, int d) { queue<int>q; memset(dep, -1, sizeof(dep)); q.push(s); dep[s] = 0; while (!q.empty()) { int u = q.front(); q.pop(); for (int i = hd[u]; i; i = e[i].nxt) { int v = e[i].t, w = e[i].wight; if (dep[v] == -1 && w > 0) { dep[v] = dep[u] + 1; q.push(v); } } } return dep[d] != -1; } ll dfs(int u, ll flow, int d) { if (u == d)return flow; ll rest = flow; for (int i = cur[u]; i; i = e[i].nxt) { cur[u] = i;//优化2,当前弧优化 //如果走到当前弧时上层给它的流量用光了, //之前的路线已经走过了,而且流量是拉满了的(即min(w,rest)取的是rest),下次就不用又走一遍了,与优化1对应起来 int v = e[i].t; ll w = e[i].wight; if (dep[v] == dep[u] + 1 && w > 0) { ll tmp = dfs(v, min(w, rest), d); rest -= tmp; e[i].wight -= tmp; e[i ^ 1].wight += tmp; if (rest == 0)break;//优化1,上一层流过来的流量已经分完了,没得分了 } } if (rest == flow)dep[u] = -1; //优化3,走这个点的所有边一条增广路都没发现,废点,炸掉 return flow - rest;//该点的流出的流量就是上一层流过来的减去剩下的流量 } ll dinic(int s, int d) { ll ans = 0; while (bfs(s, d)) { for (int i = 1; i <= d; i++) { cur[i] = hd[i]; } ans += dfs(s, inf, d); } return ans; } int n, m; ll val[maxn], sum; int main() { //freopen("test.txt", "r", stdin); int T; scanf("%d", &T); while (T--) { sum = 0; tot = 1; memset(hd, 0, sizeof(hd)); scanf("%d%d", &n, &m); for (int i = 1; i <= n + m; i++) { scanf("%lld", &val[i]); if (i <= n)sum += val[i]; } int s = n + m + 1, d = n + m + 2; for (int i = n + 1; i <= n + m; i++) { add(s, i, val[i]); int k; scanf("%d", &k); while (k--) { int t; scanf("%d", &t); add(i, t, val[t]); } } for (int i = 1; i <= n; i++) { add(i, d, val[i]); } printf("%lld\n", sum - dinic(s, d)); } return 0; }