传送门:LA 2796 Concert Hall Scheduling
题目大意:一个著名的音乐厅因为财务状况恶化快要破产,你临危受命,试图通过管理的手段来拯救它,方法之一就是优化演出安排,即聪明的决定接受和拒绝哪些乐团的演出申请,使得音乐厅的收益最大化。该音乐厅有两个完全相同的房间,因此各乐团在申请演出的时候并不会指定房间,你只需要随便分配一个即可。每个演出都会持续若干天,每个房间每天只能举行一场演出。申请数目n为不超过1000的正整数,每个申请用三个整数i, j, w描述,表示从第i天演到第j天,愿意支付w元。
接下来分成两个思路建模。
朴素建模分析:将每个乐队拆成两个点,之间建边(i,i + n,1,-w)。然后为每个房间分配一个流量,每个房间 x 向所有的乐队 i 建边(x,i,1, 0),然后每个乐队向能在他之后使用该房间的乐队 j 建边(i + n,j,1,0)。最后每个乐队向汇点建边(i + n,t,1,0)。跑一遍最小费用流,答案就是cost的相反数。
不得不说这是最容易想到的建模方法。但是效率太低了。
朴素建模代码如下:
//1012ms #include <stdio.h> #include <string.h> #include <algorithm> #define clear(A, X) memset(A, X, sizeof A) #define min(A, B) ((A) < (B) ? (A) : (B)) using namespace std; const int maxE = 2000000; const int maxN = 2048; const int oo = 0x3f3f3f3f; struct Edge{ int v, c, w, n; Edge(){} Edge(int V, int C, int W, int N) : v(V), c(C), w(W), n(N){} }; struct Node{ int l, r, w; }; Edge edge[maxE]; Node sche[maxN]; int adj[maxN], cntE; int Q[maxE], head, tail; int d[maxN], inq[maxN], cur[maxN], f[maxN]; int cost, flow, s, t; int n; void addedge(int u, int v, int c, int w){ edge[cntE] = Edge(v, c, w, adj[u]); adj[u] = cntE++; edge[cntE] = Edge(u, 0, -w, adj[v]); adj[v] = cntE++; } int spfa(){ clear(d, oo); clear(inq, 0); cur[s] = -1; f[s] = oo; d[s] = 0; head = tail = 0; Q[tail++] = s; inq[s] = 1; while(head != tail){ int u = Q[head++]; inq[u] = 0; for(int i = adj[u]; ~i; i = edge[i].n){ int v = edge[i].v, c = edge[i].c, w = edge[i].w; if(c && d[v] > d[u] + w){ d[v] = d[u] + w; f[v] = min(f[u], c); cur[v] = i; if(!inq[v]){ Q[tail++] = v; inq[v] = 1; } } } } if(d[t] == oo) return 0; flow += f[t]; cost += d[t] * f[t]; for(int i = cur[t]; ~i; i = cur[edge[i ^ 1].v]){ edge[i].c -= f[t];//这里竟然打成edge[i].v了,我今天没吃药,感觉自己萌萌哒QUQ edge[i ^ 1].c += f[t]; } return 1; } int MCMF(){ flow = cost = 0; while(spfa()); return cost; } void init(){ clear(adj, -1); cntE = 0; } void build(){ s = 0; t = n * 2 + 3; int s1 = n * 2 + 1, s2 = n * 2 + 2; addedge(s, s1, 1, 0); addedge(s, s2, 1, 0); for(int i = 1; i <= n; ++i) scanf("%d%d%d", &sche[i].l, &sche[i].r, &sche[i].w); for(int i = 1; i <= n; ++i){ addedge(s1, i, 1, 0); addedge(s2, i, 1, 0); addedge(i, i + n, 1, -sche[i].w); addedge(i + n, t, 1, 0); for(int j = 1; j <= n; ++j){ if(sche[i].r < sche[j].l) addedge(i + n, j, 1, 0); } } } void work(){ init(); build(); printf("%d\n", -MCMF()); } int main(){ while(~scanf("%d", &n) && n) work(); return 0; }
接下来是另一种:
//------------------------------------
先说一下区间 k 覆盖模型。
区间 k 覆盖问题。数轴上有一些带全值的左闭右开区间,选出权和尽量大的一些区间,使得任意一个数最多被 k 个区间覆盖。
解:首先这是可以用最小费用流解决的。构图方法是把每个数作为一个结点,然后对于权值为 w 的区间[ u,v ),建边u -> v,容量为1,费用为 -w。在对所有相邻的点建边 i -> i + 1,容量为 k,费用为0。最后,求最左点到最右点的最小费用最大流即可,其中每个流量对应一组互不相交的区间。如果数值范围太大,可以先离散化。
//------------------------------------
对于本题,其中每个乐团的一个申请 (i,j,w)可以看做一个权值为 w 的 [ i,j + 1 ) 的左闭右开区间, k 在这个题目中就是房间的个数 2 。按照区间 k 覆盖模型建边跑一遍最小费用最大流即可。
代码如下:
//3ms(这就是差距!) #include <stdio.h> #include <string.h> #include <algorithm> #define clear(A, X) memset(A, X, sizeof A) #define min(A, B) ((A) < (B) ? (A) : (B)) using namespace std; const int maxE = 2000000; const int maxN = 400; const int oo = 0x3f3f3f3f; struct Edge{ int v, c, w, n; Edge(){} Edge(int V, int C, int W, int N) : v(V), c(C), w(W), n(N){} }; struct Node{ int l, r, w; }; Edge edge[maxE]; Node sche[maxN]; int adj[maxN], cntE; int Q[maxE], head, tail; int d[maxN], inq[maxN], cur[maxN], f[maxN]; int cost, flow, s, t; int n; void addedge(int u, int v, int c, int w){ edge[cntE] = Edge(v, c, w, adj[u]); adj[u] = cntE++; edge[cntE] = Edge(u, 0, -w, adj[v]); adj[v] = cntE++; } int spfa(){ f[s] = oo; clear(d, oo); clear(inq, 0); cur[s] = -1; d[s] = 0; head = tail = 0; Q[tail++] = s; inq[s] = 1; while(head != tail){ int u = Q[head++]; inq[u] = 0; for(int i = adj[u]; ~i; i = edge[i].n){ int v = edge[i].v, c = edge[i].c, w = edge[i].w; if(c && d[v] > d[u] + w){ d[v] = d[u] + w; f[v] = min(f[u], c); cur[v] = i; if(!inq[v]){ Q[tail++] = v; inq[v] = 1; } } } } if(d[t] == oo) return 0; flow += f[t]; cost += d[t] * f[t]; for(int i = cur[t]; ~i; i = cur[edge[i ^ 1].v]){ edge[i].c -= f[t]; edge[i ^ 1].c += f[t]; } return 1; } int MCMF(){ flow = cost = 0; while(spfa()); return cost; } void init(){ clear(adj, -1); cntE = 0; } void build(){ int l, r, w; s = 0; t = 366; addedge(s, 1, 2, 0); for(int i = 1; i <= n; ++i){ scanf("%d%d%d", &l, &r, &w); addedge(l, r + 1, 1, -w); } for(int i = 1; i <= 365; ++i) addedge(i, i + 1, 2, 0); } void work(){ init(); build(); printf("%d\n", -MCMF()); } int main(){ while(~scanf("%d", &n) && n) work(); return 0; }