bzoj2127 happiness 最小割

本文采用网络流算法解决学校文理分班问题,通过构建网络模型,利用最小割原理优化分班方案,确保每个学生的选择都能最大化其满意度。文中详细解释了模型构建过程,包括点与点之间的连接、容量设定以及如何通过割断边来调整学生的选择,最终达到最优分班方案。此外,还介绍了算法的时间复杂度优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       看到这种题目,一般就是想到构建网络流,然后一种割对应一种方案(本题中为割完后S和选文科的相连,T和选理科的项链),求最小割把。。

       考虑一个点x(代表一个同学),如果选文科,就要和T割断(理科割S),割断的为损失的部分,即选理科的喜悦值,因此连边x->T,容量为选理科的喜悦值;同理连边S->x,容量为选文科的喜悦值。

       然后考虑两个相邻的点x,y,不同的方案对答案产生的影响。设u为x,y都选文科的额外喜悦值;v为x,y都选理科的额外喜悦值,flow(p,q)表示从p到q连容量为flow(p,q)的边(不包括前面已经连好的边)。那么有三种情况:

       1.x,y都选文科,那么需要将x和y到T的边的割断,各自损失的喜悦值已经减去了,那么还需要减去损失的都选理科的喜悦值。即flow(x,T)+flow(y,T)=v;

       2.x,y都选理科,同上分析可得,flow(S,x)+flow(S,y)=u;

       3.x,y一个选文科一个选理科,不妨设x选文科。那么显然都选文和都选理的额外喜悦值都损失了。这时我们发现前面两条已经把S->x,y和x,y->T的边都建好了,不能改动(否则上面就出问题了)。因此考虑在x->y或者y->x中连边。那么由于割完后S和x相连,y与T相连,那么如果有一条x->y的边,则必须要将这条边割断,因此在x->y的边上做文章。因此得到flow(S,x)+flow(x,y)+flow(y,T)=u+v;同理得到flow(S,y)+flow(y,x)+flow(x,T)=u+v。

       然后就发现这需要flow(S,x)=flow(S,y)=u/2,flow(x,T)=flow(y,T)=v/2,flow(x,y)=flow(y,x)=(u+v)/2即可。那么这些边再加上前面的边就是完整的建图了。操作时把起点和终点相同的边合并可以加快速度。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100005
#define inf 1000000000
using namespace std;

int n,m,tot=1,gol,a[6][105][105],fst[N],pnt[N],len[N],nxt[N],d[N],h[N];
int pt(int x,int y){ return (x-1)*n+y; }
void add(int x,int y,int z){
	pnt[++tot]=y; len[tot]=z; nxt[tot]=fst[x]; fst[x]=tot;
}
void ins(int x,int y,int z,int p){ add(x,y,z); add(y,x,z*p); }
bool bfs(){
	int head=0,tail=1; h[1]=0;
	memset(d,-1,sizeof(d)); d[0]=1;
	while (head<tail){
		int x=h[++head],p;
		for (p=fst[x]; p; p=nxt[p]) if (len[p]){
			int y=pnt[p];
			if (d[y]==-1){ d[y]=d[x]+1; h[++tail]=y; }
		}
	}
	return d[gol]!=-1;
}
int dfs(int x,int rst){
	if (x==gol || !rst) return rst; int p,flow=0;
	for (p=fst[x]; p; p=nxt[p]) if (len[p]){
		int y=pnt[p]; if (d[x]+1!=d[y]) continue;
		int tmp=dfs(y,min(rst,len[p])); if (!tmp) continue;
		flow+=tmp; len[p]-=tmp;
		len[p^1]+=tmp; rst-=tmp; if (!tmp) break;
	}
	if (!flow) d[x]=-1; return flow;
}
int main(){
	scanf("%d%d",&m,&n); int i,j,k,sum=0,ans=0;
	for (k=0; k<6; k++){
		int x=m,y=n;
		if (k>3) y--; else if (k>1) x--;
		for (i=1; i<=x; i++)
			for (j=1; j<=y; j++){
				scanf("%d",&a[k][i][j]); sum+=a[k][i][j];
			}
	}
	gol=m*n+1;
	for (i=1; i<=m; i++)
		for (j=1; j<=n; j++){
			ins(0,pt(i,j),(a[0][i][j]<<1)+a[2][i][j]+a[2][i-1][j]+a[4][i][j]+a[4][i][j-1],0);
			ins(pt(i,j),gol,(a[1][i][j]<<1)+a[3][i][j]+a[3][i-1][j]+a[5][i][j]+a[5][i][j-1],0);
			if (i<m) ins(pt(i,j),pt(i+1,j),a[2][i][j]+a[3][i][j],1);
			if (j<n) ins(pt(i,j),pt(i,j+1),a[4][i][j]+a[5][i][j],1);
		}
	while (bfs()) ans+=dfs(0,inf);
	printf("%d\n",sum-(ans>>1));
	return 0;
}


by lych

2016.3.11

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值