LA 2796 Concert Hall Scheduling 费用流

传送门: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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值