费用流


费用流模板 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值