費用流


費用流模板 LOJ

LOJ上模板68ms還挺快 

點擊打開鏈接

要跑一遍最大費用最大流和最小費用最大流 每次跑之前重新建圖 範圍很小沒什麼問題

每次SPFA找出最短/最長路 然後blabla 把費用W當成邊權

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 5007;
#define Min(_A,_B) (_A>_B?_B:_A)
const int inf = 0x3f3f3f3f;
struct edge {
	int v, c, w, nxt;
}e[maxn << 1];

int head[maxn], eid = 0, d[maxn], pre[maxn], s, t, a[101], b[101], n, m, h[101][101];
bool inq[maxn];
//帶權二分圖匹配
void insert(int u, int v, int c, int w) {
	e[eid].v = v;  e[eid].w = w; e[eid].c = c;e[eid].nxt = head[u];  head[u] = eid++;
	e[eid].v = u;  e[eid].nxt = head[v];  e[eid].w = -w; e[eid].c = 0;  head[v] = eid++;
}

bool spfa() {
	memset(inq, 0, sizeof(inq));
	memset(d, -inf, sizeof(d));
	memset(pre, -1, sizeof(pre));
	queue<int> q;
	q.push(s);
	inq[s] = true;
	d[s] = 0;
	int u, v, i;
	while (!q.empty()) {
		u = q.front(); q.pop();
		inq[u] = false;
		for (i = head[u]; ~i; i = e[i].nxt) {
			if (e[i].c) {
				v = e[i].v;
				if (d[u] + e[i].w>d[v]) {//最長路解決最大費用最大流問題
					d[v] = d[u] + e[i].w;
					pre[v] = i;
					if (!inq[v]) {
						inq[v] = true;
						q.push(v);
					}
				}
			}
		}
	}
	return pre[t] != -1;
}
bool spfa_min() {
	memset(inq, 0, sizeof(inq));
	memset(d, inf, sizeof(d));
	memset(pre, -1, sizeof(pre));
	queue<int> q;
	q.push(s);
	inq[s] = true;
	d[s] = 0;
	int u, v, i;
	while (!q.empty()) {
		u = q.front(); q.pop();
		inq[u] = false;
		for (i = head[u]; ~i; i = e[i].nxt) {
			if (e[i].c) {
				v = e[i].v;
				if (d[u] + e[i].w<d[v]) {//最短路解決最小費用最大流問題
					d[v] = d[u] + e[i].w;
					pre[v] = i;
					if (!inq[v]) {
						inq[v] = true;
						q.push(v);
					}
				}
			}
		}
	}
	return pre[t] != -1;
}

int costflow() {
	int res = 0;
	while (spfa()) {
		int i, flow = inf;
		for (i = t; i != s; i = e[pre[i] ^ 1].v) {
			flow = Min(flow, e[pre[i]].c);//尋找路徑上的最小流量
		}
		for (i = t; i != s; i = e[pre[i] ^ 1].v) {
			e[pre[i] ^ 1].c += flow;
			e[pre[i]].c -= flow;
			res += e[pre[i]].w*flow;
		}
	}
	return res;
}
int costflow_min() {
	int res = 0;
	while (spfa_min()) {
		int i, flow = inf;
		for (i = t; i != s; i = e[pre[i] ^ 1].v) {
			flow = Min(flow, e[pre[i]].c);
		}
		for (i = t; i != s; i = e[pre[i] ^ 1].v) {
			e[pre[i] ^ 1].c += flow;
			e[pre[i]].c -= flow;
			res += e[pre[i]].w*flow;
		}
	}
	return res;
}
inline int read() {
	int s = 0, f = 1; char c = getchar(); while (c<'0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
	while (c >= '0'&&c <= '9') { s = s * 10 + c - '0'; c = getchar(); }
	return s*f;
}

int main() {
	m = read(); n = read(); int i, j;
	memset(head, -1, sizeof(head));
	s = 0; t = n+m+1;
	for (i = 1; i <= m; i++) {
		a[i] = read();
		insert(s, i, a[i], 0);
	}
	for (i = 1; i <= n; i++) {
		b[i] = read();
		insert(i + m, t, b[i], 0);
	}

	for (i = 1; i <= m; i++)
		for (j = 1; j <= n; j++) {
			h[i][j] = read();
			insert(i, j + m, inf, h[i][j]);
	}
	printf("%d\n", costflow_min());
	eid = 0;
	memset(head, -1, sizeof(head));
	for (i = 1; i <= m; i++) insert(s, i, a[i], 0);
	for (i = 1; i <= n; i++) insert(i + m, t, b[i], 0);
	for (i = 1; i <= m; i++)
		for (j = 1; j <= n; j++)
			insert(i, j + m, inf, h[i][j]);
	printf("%d", costflow());
	//getchar();
	return 0;
}

SCOI2007 修車

拆點 把每個師傅拆成n個點表示倒數第i個修的是xxx

比如師傅i按順序修了三輛車,時間分別是T1,T2,T3,那麼總時間是T1+(T1+T2) +(T1+T2+T3)

那麼可以設倒數第i個修的車對應的弧費用爲T*i

BZOJ上TLE了 時限1s過不去 但是洛谷AC了

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define Max(_A,_B) (_A>_B?_A:_B)
#define Min(_A,_B) (_A>_B?_B:_A)
const int inf = 0x3f3f3f3f;
using namespace std;
const int maxn = 370007;//10*60*60
int n, m;
struct edge {
	int v, w, c, nxt;
}e[maxn<<1];
int p[maxn], eid = 0, dis[maxn],pre[maxn],s,t;
bool inq[maxn];
void insert(int u, int v, int c, int w) {
	e[eid].v = v; 
	e[eid].w = w;
	e[eid].c = c;
	e[eid].nxt = p[u];
	p[u] = eid++;
}
void addedge(int u, int v, int c, int w) {
	insert(u, v, c, w);
	insert(v, u, 0, -w);
}
bool spfa() {
	queue<int> q;
	q.push(s);
	memset(dis, inf, sizeof(dis));
	memset(pre, -1, sizeof(pre));
	memset(inq, false, sizeof(inq));
	dis[s] = 0;
	inq[s] = true;
	while (!q.empty()) {
		int u = q.front(); 
		q.pop();
		inq[u] = false;
		for (int i = p[u]; i!=-1; i = e[i].nxt){
			if (e[i].c) {
				int v = e[i].v;
				if (dis[u] + e[i].w < dis[v]) {
					dis[v] = dis[u] + e[i].w;
					pre[v] = i;
					if (!inq[v]) {
						inq[v] = true;
						q.push(v);
					}
				}
			}
		}
	}
	return pre[t] != -1;
}
double cost_flow() {
	double res = 0;
	while (spfa()) {
		int flow = inf;
		for (int i = t; i != s; i = e[pre[i] ^ 1].v) {
			flow = Min(flow, e[pre[i]].c);
			//printf("1");
		}
		for (int i = t; i != s; i = e[pre[i] ^ 1].v) {
			e[pre[i]].c -= flow;
			e[pre[i] ^ 1].c += flow;
			res += e[pre[i]].w*flow;
		}
	}

	return res;
}
inline int read() {
	int s = 0, f = 1; char c = getchar(); while (c<'0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
	while (c >= '0'&&c <= '9') { s = s * 10 + c - '0'; c = getchar(); }
	return s*f;
}
int g[61][11];
int main() {
	m = read();
	n = read();
	s = 0; t = m*n+n+1;
	memset(p, -1, sizeof(p));
	for(int i=1;i<=n;i++)
		for (int j = 1; j <= m; j++) {
			g[i][j] = read();
		}
	for (int i = 1; i <= n*m; i++) addedge(s, i, 1, 0);
	for (int i = n*m + 1; i <= n*m + n; i++)
		addedge(i, t, 1, 0);
	for (int i = 1; i <= m; i++)
		for (int j = 1; j <= n; j++)//次序
			for (int k = 1; k <= n; k++)//客人編號
				addedge((i - 1)*n + j, m*n + k, 1, g[k][i] * j);
	printf("%.2lf\n", cost_flow()/n );
	//getchar();
	return 0;
}

NOI2012 美食節

建圖方法和SCOI2007修車一樣 但是數據更強了需要動態連邊
然後注意到可以不把每道菜拆成P[i]個點 而是把S點連向表示該到菜的邊的容量設成P[i] 就可以沿這條弧增廣P[i]次了
在費用流過程中計算邊對應的廚師和菜的次序時取模炸了 被m=1的數據卡掉了  發現原來的取模方式無法算出廚師做的第$\sum P$道菜 要特判 調了好久/_ \
動態連邊的原理是,如果最小費用最大流增廣路經過了廚師i做的倒數第j道菜的點 之前就一定經過了倒數第j-1道菜

因爲倒數第1道菜的對應弧容量最小 (按修車那題的建模方式)

時限有點緊 還可以在spfa的同時預處理出對應增廣路的流量 記爲incf

#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define Min(_A,_B) (_A<_B?_A:_B)
const int maxn = 81007,inf=0x3f3f3f3f;
struct edge {
	int v, w, c, nxt;
}e[500007];
int head[maxn], eid = 0, d[maxn],s,
t,n,m,p[maxn],u,v,pre[maxn],incf[maxn],sp;
bool inq[maxn];
int g[41][101];
void insert(int u, int v, int c, int w) {
	e[eid].v = v; 
	e[eid].c = c; 
	e[eid].w = w; 
	e[eid].nxt = head[u];
	head[u]=eid++;
}
void addedge(int u, int v, int c, int w) {
	insert(u, v, c, w);
	insert(v, u, 0, -w);
}
void init() {
	memset(head, -1, sizeof(head));
	eid = 0;
}
/*動態加邊的思路是 如果增廣路流經x->t 與x相連的菜
表示廚師j做的倒數第x-n-(j-1)*p道菜
根據此題的構圖
	廚師i做的第j道菜耗時g[j][i]
	第j+1道菜耗時g[j+1][i]+g[j][i]
	第j+2道菜耗時g[j+2][i]+g[j+1][i]+g[j][i]
	總時長爲g[j][i]*3+g[j+1][i]*2+g[j+2][i]
	故我們設各個菜與S相連 容量爲pi
	廚師與t相連 每個廚師j佔的點編號範圍爲
	n+p*(j-1)+1到n+p*j
	即把每個廚師拆成至多p個點。
有T[x]<T[x+1] 如果沒有連向x 就不加連向x+1的邊
*/
bool spfa() {
	memset(d, inf, sizeof(d));
	memset(inq, false, sizeof(inq));
	memset(pre, -1, sizeof(pre));
	queue<int> q;q.push(s);
	d[s] = 0;inq[s] = true;incf[s] = inf;
	while (!q.empty()) {
		u = q.front(); q.pop();inq[u] = false;
		for (int i = head[u]; ~i; i = e[i].nxt) {
				if (e[i].c>0&&d[e[i].v] > d[u] + e[i].w) {
					v=e[i].v;
					d[v] = d[u] + e[i].w;pre[v] = i;
					incf[v] = Min(incf[u], e[i].c);
					if (!inq[v]) {	inq[v] = true;q.push(v);}
				}
		}
	}
	return pre[t]!=-1;
}
int ans = 0,x;
void cost_flow() {
	while(spfa()){
		for(int i=t;i!=s;i=e[pre[i]^1].v){
			e[pre[i]].c-=incf[t];
			e[pre[i]^1].c+=incf[t];
		}
		ans+=d[t]*incf[t];
		x=e[pre[t]^1].v;//增廣路連向t的邊(廚師拆出的點)
		//獲取廚師編號j:由x=n+(j-1)*p+k知,
		//j=(x-n-1)/p+1
		//廚師現在是倒數第k道菜
		//k=(x-n-1)%p+1
		int a=(x-n)/sp+1,b=(x-n+1)%sp;
		if(!b) b=sp;
		//printf("%d %d %d\n",x,a,b);
		addedge(x+1,t,1,0);
		for(int i=1;i<=n;i++){
			addedge(i,x+1,1,g[i][a]*b);
		}

	}
}
inline int read(){
	int s=0,f=1;char c=getchar();while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9') {s=s*10+c-'0';c=getchar();}
	return s*f;
}
int main(){
	n=read();m=read();
	s=0;
	init();
	for(int i=1;i<=n;i++){
		p[i]=read();sp+=p[i];
		addedge(s,i,p[i],0);//p[i]道菜匹配p[i]次
	}
	t=sp*m+n+1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){	
			g[i][j]=read();
			//連接每個廚師做的倒數第一道菜
		}
	for(int j=1;j<=m;j++){
		x=n+(j-1)*sp+1;//廚師j做的倒數第1道菜與x連
		for(int i=1;i<=n;i++){
			addedge(i,x,1,g[i][j]);
		}
		addedge(x,t,1,0);
	}
	//printf("%d\n",sp);
	cost_flow();
	printf("%d",ans);
	return 0;
}

發佈了29 篇原創文章 · 獲贊 8 · 訪問量 3176
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章