solved 1/13
2016 Multi-University Training Contest 9
二分+最大权闭合图 Less Time, More profit(BH)
题意就是有n个工厂,m个商店
每个工厂有建造时间ti,花费payi
每个商店和k个工厂有关,如果这k个工厂都建造了,那么能获利proi
问你求收益(∑pro−∑pay)≥L时,首先满足时间t最小,其次是收益p最大首先二分时间的答案,然后看那些工厂能建造,然后工厂是花费,商店是收益,并且要与商店有关的工厂都建造了,才能获利,所以这是一个最大权闭合图的问题。
关于最大权闭合图:岐哥的博客
闭合图就是闭合图中的点的后继也在这个闭合图中。而闭合图是反映事物间必要条件的关系的,也就是说一个点是它后继的必要条件。也就是说要有这个点,它的后继点也要都存在。
最大权闭合图:
建图:构造一个源点S,汇点T。我们将S与所有权值为正的点连一条容量为其权值的边,将所有权值为负的点与T连一条容量为其权值的绝对值的边,原来的边将其容量定为正无穷。
求解:最大权闭合图=正的点权和-最小割=正的点权和-最大流。
时间复杂度为.
代码:
#include <bits/stdc++.h> const int N = 200 + 5; const int INF = 0x3f3f3f3f; int pay[N], t[N]; int pro[N]; std::vector<int> need[N]; int n, m; int L; /* *最大流之Dinic算法:不停地构造层次图,然后用阻塞流增广。 *时间复杂度为 O(n^2*m)。 *如果容量为1,复杂度为 O(min(n^(2/3), m^(1/2)*m)。 *对于二分图最大匹配这样的特殊图,复杂度为 O(n^(1/2)*m) */ struct Max_Flow { struct Edge { int from, to, cap, flow; }; std::vector<Edge> edges; std::vector<int> G[2*N]; //保存每个结点的弧在edges里的序号 int level[2*N], cur[2*N]; //结点在层次图中的等级。结点当前弧下标。 int n, m, s, t; //初始化顶点个数n,边的个数m在加边时统计,s和t分别为源点和汇点 void init(int n) { this->n = n; for (int i=0; i<=n; ++i) { G[i].clear (); } edges.clear (); } void add_edge(int from, int to, int cap) { edges.push_back ((Edge) {from, to, cap, 0}); edges.push_back ((Edge) {to, from, 0, 0}); m = edges.size (); G[from].push_back (m - 2); G[to].push_back (m - 1); } bool BFS() { std::fill (level, level+1+n, -1); std::queue<int> que; level[s] = 0; que.push (s); while (!que.empty ()) { int u = que.front (); que.pop (); for (int i=0; i<G[u].size (); ++i) { Edge &e = edges[G[u][i]]; if (level[e.to] == -1 && e.cap > e.flow) { level[e.to] = level[u] + 1; que.push (e.to); } } } return level[t] != -1; } int DFS(int u, int a) { if (u == t || a == 0) { return a; //a表示当前为止所有弧的最小残量 } int flow = 0, f; for (int &i=cur[u]; i<G[u].size (); ++i) { Edge &e = edges[G[u][i]]; if (level[u] + 1 == level[e.to] && (f = DFS (e.to, std::min (a, e.cap - e.flow))) > 0) { e.flow += f; edges[G[u][i]^1].flow -= f; flow += f; a -= f; if (a == 0) { break; } } } return flow; } int Dinic(int s, int t) { this->s = s; this->t = t; int flow = 0; while (BFS ()) { std::fill (cur, cur+1+n, 0); flow += DFS (s, INF); } return flow; } }max_flow; int check(int max_t) { int S = 0, T = n + m + 1; int sum = 0; max_flow.init (n+m+2); for (int i=1; i<=m; ++i) { max_flow.add_edge (S, i, pro[i]); bool flag = true; for (int j: need[i]) { if (t[j] > max_t) { flag = false; break; } } if (flag) { for (int j: need[i]) { max_flow.add_edge (i, m+j, INF); } sum += pro[i]; } } for (int i=1; i<=n; ++i) { if (t[i] <= max_t) max_flow.add_edge (m+i, T, pay[i]); //abs (-pay[i]) } int min_cut = max_flow.Dinic (S, T); if (sum - min_cut >= L) return sum - min_cut; else return -1; } void solve(int cas) { printf ("Case #%d: ", cas); int best_t = INF, best_p = 0; int l = 1, r = 1000000000; while (l <= r) { int mid = l + r >> 1; int res = check (mid); if (res != -1) { best_t = std::min (best_t, mid); best_p = res; r = mid - 1; } else l = mid + 1; } if (best_t < INF) printf ("%d %d ", best_t, best_p); else puts ("impossible"); } int main() { int T; scanf ("%d", &T); for (int cas=1; cas<=T; ++cas) { scanf ("%d%d%d", &n, &m, &L); for (int i=1; i<=n; ++i) { scanf ("%d%d", pay+i, t+i); } for (int i=1; i<=m; ++i) { scanf ("%d", pro+i); need[i].clear (); int k; scanf ("%d", &k); while (k--) { int id; scanf ("%d", &id); need[i].push_back (id); } } solve (cas); } return 0; }
还有贪心的做法,时间复杂度,解释。
代码:
#include <bits/stdc++.h> const int N = 200 + 5; const int INF = 0x3f3f3f3f; struct Plant { int pay, t; }p[N]; std::vector<int> id[N]; struct Shop { int pro; std::vector<int> need; int pay; int max_t; int done; }s[N]; int n, m; int L; bool vis_p[N], vis_s[N]; int check(int max_t) { memset (vis_p, false, sizeof (vis_p)); memset (vis_s, false, sizeof (vis_s)); for (int i=0; i<m; ++i) s[i].done = 0; int ret = -INF, tmp = 0; for (; ;) { int max_val = -INF, k = -1; for (int i=0; i<m; ++i) { if (!vis_s[i] && s[i].max_t <= max_t) { if (max_val < s[i].pro - s[i].pay + s[i].done) { max_val = s[i].pro - s[i].pay + s[i].done; k = i; } } } if (max_val == -INF) break; vis_s[k] = true; //max m times tmp += max_val; ret = std::max (ret, tmp); for (int j: s[k].need) { if (!vis_p[j]) { vis_p[j] = true; for (int l: id[j]) { if (!vis_s[l]) { s[l].done += p[j].pay; } } } } } return ret >= L ? ret : -1; } void solve(int cas) { printf ("Case #%d: ", cas); int best_t = INF, best_p = 0; int l = 1, r = 1000000000; while (l <= r) { int mid = l + r >> 1; int res = check (mid); if (res != -1) { best_t = std::min (best_t, mid); best_p = res; r = mid - 1; } else l = mid + 1; } if (best_t < INF) printf ("%d %d ", best_t, best_p); else puts ("impossible"); } int main() { int T; scanf ("%d", &T); for (int cas=1; cas<=T; ++cas) { scanf ("%d%d%d", &n, &m, &L); for (int i=0; i<n; ++i) { scanf ("%d%d", &p[i].pay, &p[i].t); id[i].clear (); } for (int i=0; i<m; ++i) { scanf ("%d", &s[i].pro); s[i].need.clear (); s[i].max_t = -1; s[i].pay = 0; int k; scanf ("%d", &k); while (k--) { int j; scanf ("%d", &j); j--; if (p[j].t > s[i].max_t) s[i].max_t = p[j].t; s[i].pay += p[j].pay; s[i].need.push_back (j); id[j].push_back (i); } } solve (cas); } return 0; }