FZU - 2295 Human life (最大权闭合子图)
Posted winter-bamboo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FZU - 2295 Human life (最大权闭合子图)相关的知识,希望对你有一定的参考价值。
题目链接
题目分析
题意:你在玩一个游戏,在其中你可以通过学习一些技能,但是学习某些技能之前,可能还要学习一些其他的技能,并且学习任何技能都有一定的花费;
而我们可以通过掌握某些工作以获取报酬,为了掌握这一工作,我们必须学会特定的技能。
不过有些工作彼此之间是冲突的,简单来说:如果你掌握了工作A,那么将无法掌握工作B
思路:
由于技能之间也存在依赖关系,但实际上如果要获取某一工作的报酬,那么必须选择这个工作的前置技能以及前置技能的前置技能,
那么显然,这些形成依赖关系的技能都是这一工作的前置技能,所以我们的问题就是求最大权闭合子图了。
我们在工作和其所有的前置技能之间建一条容量为inf的边,然后由所有的技能向汇点建一条容量为这一技能学习消耗的边,
再由源点向每个工作建一条容量为这一工作报酬的边。
这个地方还加上了一个条件,有些工作无法同时获取,不过这个地方产生冲突最大对数为5个,那么我们枚举所有的情况就好了,
一共2^5 = 32种,根据每种状态来决定可选取的工作,并构建这一顶点及其相关的边
然后根据:最大闭合子图的权值 = 所有权值为正的结点的权值之和 - 最小割(最大流)求出答案
顺便吐糟一下这个题,首先这个OJ的C++环境不是C11标准,也就是说不支持大括号给结构体变量赋值,比如这样:
我这个语法写了近一年了,从未出错,这个评测机第一次把我这里卡了,一直CE,还不给出错误信息QAQ....
代码区

#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #include<string> #include<fstream> #include<vector> #include<stack> #include <map> #include <iomanip> #define bug cout << "**********" << endl #define show(x,y) cout<<"["<<x<<","<<y<<"] " //#define LOCAL = 1; using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const int mod = 1e9 + 7; const int Max = 1e3+ 10; struct Edge int to, next, flow; edge[Max << 2]; int T, n, m, k; int s, t; vector<int>edge_raw[Max]; //记录原图边的关系 int vis[2010][1010]; //vis[i][j]记录j是否是i的前置结点 int select[2010]; //记录某点是否被删除(处理工作冲突) int use[1010]; //记录每个点的是否使用(处理技能之间的前置关系) int head[2010], tot; int cost[1010]; //记录了学习每个技能的花费 int val[2010]; //记录掌握每个工作的报酬 int dis[2010]; //记录i的层次编号(Dinic中使用) pair<int, int>fight[10]; //记录冲突 void init() s = 0, t = n + m + 1; //1-m为技能,m+1~n+m为工作 memset(vis, 0, sizeof(vis)); memset(use, 0, sizeof(use)); for (int i = 0;i <= n; i++) edge_raw[i].clear(); void add(int u, int v, int flow) edge[tot].to = v; edge[tot].flow = flow; edge[tot].next = head[u]; head[u] = tot++; edge[tot].to = u; edge[tot].flow = 0; edge[tot].next = head[v]; head[v] = tot++; void dfs(int u) //找到每个技能所有的前置技能 use[u] = true; //代表u已经访问 for (int i = 0; i < edge_raw[u].size(); i++) int v = edge_raw[u][i]; vis[u][v] = true; if (!use[v]) //自己是自己的前置结点 dfs(v); for (int j = 1;j <= n;j++) //枚举这个点的前置结点 if (vis[v][j]) //v的前置结点是j,那么u的前置结点也是j vis[u][j] = true; bool bfs() //判断连通性,将图分层次 queue<int>q; memset(dis, -1, sizeof(dis)); dis[s] = 0; q.push(s); while (!q.empty()) int u = q.front();q.pop(); for (int i = head[u]; i != -1; i = edge[i].next) int v = edge[i].to; if (dis[v] == -1 && edge[i].flow > 0) dis[v] = dis[u] + 1; q.push(v); if (v == t) return true; return false; int dfs(int u, int flow_in) if (u == t) return flow_in; int flow_out = 0; //实际流出流量 for (int i = head[u];i != -1;i = edge[i].next) int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].flow > 0) int flow_part = dfs(v, min(flow_in, edge[i].flow)); if (flow_part == 0)continue; //无法形成增广路 flow_in -= flow_part; flow_out += flow_part; edge[i].flow -= flow_part; edge[i ^ 1].flow += flow_part; if (flow_in == 0)break; return flow_out; int max_flow() int sum = 0; while (bfs()) sum += dfs(s, inf); return sum; int main() #ifdef LOCAL freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); #endif scanf("%d", &T); while (T--) scanf("%d%d%d", &n, &m, &k); init(); for (int i = 1, num;i <= n;i++) scanf("%d%d", cost + i, &num); for (int j = 1;j <= num;j++) int pre; //技能i的前置技能点 scanf("%d", &pre); edge_raw[i].push_back(pre); //构建直系前置技能关系 for (int i = 1;i <= n;i++) if (!use[i])dfs(i); for (int i = n + 1, cnt; i <= n + m;i++) //工作编号n +1 ~n+m scanf("%d%d", val + i, &cnt); while (cnt--) int x; scanf("%d", &x); vis[i][x] = true; for (int j = 1;j <= n;j++) if (vis[x][j]) //求出工作所有的前置技能 vis[i][j] = true; for (int i = 0;i < k;i++) scanf("%d %d", &fight[i].first, &fight[i].second); int max_val = 0; for (int state = 0;state < (1 << k);state++) //枚举状态,对应位置为1表示选first,0表示选second memset(select, 0, sizeof(select)); //0表示不删除 memset(head, -1, sizeof(head));tot = 0; for (int i = 0;i < k;i++) if ((state >> i) & 1) select[fight[i].second] = true; //删除second else select[fight[i].first] = true; //删除first int sum = 0; //记录总价值 for (int i = 1 + n;i <= n + m;i++) if (select[i - n])continue; //当前状态下不不选取的工作就不用构建与之有关的边了 sum += val[i]; add(s, i, val[i]); //由源点向可选工作构建一条容量为当前工作报酬的边 for (int j = 1;j <= n;j++) if (vis[i][j]) add(i, j, inf); //有工作向其所有前置技能点建一条容量为inf的边 for (int i = 1;i <= n;i++) //由所有技能向汇点构建一条容量为其花费的边 add(i, t, cost[i]); int flow = max_flow(); max_val = max(max_val, sum - flow); //最大闭合子图的权值 = 所有权值为正的结点的权值之和 - 最小割(最大流) printf("%d\\n", max_val); return 0;
以上是关于FZU - 2295 Human life (最大权闭合子图)的主要内容,如果未能解决你的问题,请参考以下文章
fzu 2039 Pets (简单二分图 + (最大流 || 二分图))
Human Gene Functions(poj 1080)