[P1646][国家集训队]happiness(最小割)

建图,使源点为文,汇点为理,初始总喜悦度减可获得的最大喜悦度相当于图的最小割。

先从源点向每个人连流量为选文喜悦度的边,且从每个人向汇点连流量为选理喜悦度的边。然后对于没一组相邻的同学都单独建一个点,若是同选文,就从源点向该点连流量为额外喜悦度的边,且从该点向其所代表的两个人连流量为无线的边,选理道理相似。这样保证了只要任意一人不选该科,就不会获得额外喜悦度。然后跑最大流即可。

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N=50000,M=300000,inf=0x7fffffff;
namespace io{
	const int SIZE=(1<<21)+1;
	char ibuf[SIZE],*iS,*iT,obuf[SIZE],*oS=obuf,*oT=oS+SIZE-1,c,qu[55];int qr;
	#define gc() (iT==iS?(iT=(iS=ibuf)+fread(ibuf,1,SIZE,stdin),iT==iS?EOF:*iS++):*iS++)
	inline void flush(){
		fwrite(obuf,1,oS-obuf,stdout);
		oS=obuf;
	}
	inline void putc(char x){
		*oS++ =x;
		if(oS==oT)flush();
	}
	template <class I>
	inline void gi(I &x){
		for(c=gc();c<'0'||c>'9';c=gc());
		for(x=0;c>='0'&&c<='9';c=gc())x=x*10+(c&15);
	}
	template <class I>
	inline void print(I &x){
		if(!x)putc('0');
		while(x)qu[++qr]=x%10+'0',x/=10;
		while(qr)putc(qu[qr--]);
	}
}
using io::gi;
using io::putc;
using io::print;
struct edge{
	int y,f,next;
}data[M];
int n,m,s,t,num,num1,tot,h[N],dep[N],cur[N];
queue<int> q;
inline void addedge(int x,int y,int f){
	data[++num].y=y,data[num].f=f,data[num].next=h[x],h[x]=num;
	data[++num].y=x,data[num].f=0,data[num].next=h[y],h[y]=num;
}
inline int min1(int a,int b){
	return a<b?a:b;
}
inline int bfs(){
	memset(dep,0,sizeof dep);
	for(int i=s;i<=num1;++i)cur[i]=h[i];
	while(!q.empty())q.pop();
	dep[s]=1,q.push(s);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=h[u];i!=-1;i=data[i].next){
			int v=data[i].y;
			if(!dep[v]&&data[i].f>0)dep[v]=dep[u]+1,q.push(v);
		}
	}
	return dep[t];
}
int dfs(int u,int t,int lim){
	if(u==t)return lim;
	int res=0;
	for(int &i=cur[u];i!=-1;i=data[i].next){
		int v=data[i].y;
		if(dep[v]==dep[u]+1&&data[i].f>0){
			int fmax=dfs(v,t,min1(lim-res,data[i].f));
			if(fmax){data[i].f-=fmax,data[i^1].f+=fmax,res+=fmax;if(res==lim)return res;}
		}
	}
	return res;
}
inline int dinic(){
	int ans=0;
	while(bfs())ans+=dfs(s,t,inf);
	return ans;
}
int main(){
	gi(n),gi(m);s=0,t=1,tot=0;
	memset(h,-1,sizeof h),num=-1;
	for(int y=1,i=1;i<=n;++i)for(int x,j=1;j<=m;++j)gi(x),++y,tot+=x,addedge(s,y,x);
	for(int y=1,i=1;i<=n;++i)for(int x,j=1;j<=m;++j)gi(x),++y,tot+=x,addedge(y,t,x);
	num1=n*m+1;
	for(int i=1;i<n;++i)
		for(int x,j=1;j<=m;++j){
			gi(x),++num1,tot+=x,addedge(s,num1,x),
			addedge(num1,(i-1)*m+j+1,inf),addedge(num1,i*m+j+1,inf);
		}
	for(int i=1;i<n;++i)
		for(int x,j=1;j<=m;++j){
			gi(x),++num1,tot+=x,addedge(num1,t,x),
			addedge((i-1)*m+j+1,num1,inf),addedge(i*m+j+1,num1,inf);
		}
	for(int i=1;i<=n;++i)
		for(int x,j=1;j<m;++j){
			gi(x),++num1,tot+=x,addedge(s,num1,x),
			addedge(num1,(i-1)*m+j+1,inf),addedge(num1,(i-1)*m+j+2,inf);
		}
	for(int i=1;i<=n;++i)
		for(int x,j=1;j<m;++j){
			gi(x),++num1,tot+=x,addedge(num1,t,x),
			addedge((i-1)*m+j+1,num1,inf),addedge((i-1)*m+j+2,num1,inf);
		}
	printf("%d",tot-dinic());
	io::flush();
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值