Luogu P4313 文理分科

link

最小割

双倍经验

这道题运用了最小割最常用的一种用法:集合划分

因为源汇最小割即就是将源汇划分到不同的集合,那么最简单的应用就是最小代价划分集合了。

本题中,题意是将 \(n\cdot m\) 个学生划分文理科,每人只能选一科且选不同的科有不同的收益,求最大收益,符合集合划分的条件,就理所当然地想到了最小割。

至于求最大收益,不妨就先将所有收益加起来,再减去最小代价(即最小割),便是最大收益了。

但是本题的难点在于,如果相邻同学选一样的(以下称为一个组合),还会有额外收益。

于是我们需要加一点限制,使得我们在最后求最小割的时候,对于每一个组合:要么满足组合内的所有成员,都在同一个子集(包含源点的子集 \(S\) 或包含汇点的子集 \(T\)),且那条代表额外收益的边不会被割掉;要么不满足组合内是所有成员,都在同一个子集,且那条代表额外收益的边被割掉了

于是大致见图思路出来了:

  • 对于每一个点(每一位同学)\(i\)

    \(s\rightarrow i\) 容量为 \(art_i\)

    \(i\rightarrow t\) 容量为 \(science_i\)

  • 对于每一个组合 \(i\),新建两个点 \(x_i,y_i\)

    \(s\rightarrow x_i\) 容量为 \(same\_art_i\)

    \(y_i\rightarrow t\) 容量为 \(same\_science_i\)

    对于该组合内的每个点(即该点+上下左右四个点)\(j\in i\)

    \(x_i\rightarrow j\) 容量为 \(+\infty\)

    \(j\rightarrow y_i\) 容量为 \(+\infty\)

这里解释一下这么连的原因:

如果要 \(s\rightarrow x_i\) 这条边(即要这个组合所有同学都选文科的收益),那么就不割这条边。但是,又因为 \(x_i\) 向这个组合内每个点都连了一条 \(+\infty\) 的边,所以这些边便不会被割掉。那么为了防止 \(s\)\(t\) 联通,自然就会割掉这个组合内每个点与 \(t\) 连的边(即都不选理科)。反之都选理科亦然。

如果放弃这个组合(即这个组合内每个成员选的科不都一样),那么就会割掉 \(s\rightarrow x_i\)\(y_i\rightarrow t\) 这两条边,那么就相当于这个组合内的每个点都互相独立了,可以任意选科。

:不能将 \(x_i\)\(y_i\) 合并成一个点来连边,这样会使上述放弃组合的情况无法达到(即无法破坏组合独立选择)。

Code

#include<bits/stdc++.h>
//#define int long long
#define pair pair<int,int>
using namespace std;
inline void end()
{
	puts("");
	system("pause");
}
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*f;
}
const int N=3e4+4,M=5e5+5;
int n,m,nm,s,t,ans,Maxflow;
int first[N],nex[M],to[M],w[M],num=1;
inline void add(int u,int v,int val)
{
	nex[++num]=first[u];
	first[u]=num;
	to[num]=v;
	w[num]=val;
}
inline void Add(int u,int v,int val)
{
	add(u,v,val);
	add(v,u,0);
}
namespace ISAP
{
	int dep[N],gap[N],cur[N];
	void bfs()
	{
		memset(dep,-1,sizeof(dep));
		memset(gap,0,sizeof(gap));
		queue<int> q;
		q.push(t);
		dep[t]=0;gap[0]=1;
		while(!q.empty())
		{
			int u=q.front();q.pop();
			for(int i=first[u];i;i=nex[i])
			{
				int v=to[i];
				if(dep[v]!=-1) continue;
				dep[v]=dep[u]+1;
				gap[dep[v]]++;
				q.push(v);
			}
		}
	}
	inline int dfs(int u,int in)
	{
		if(u==t) return in;
		int out=0;
		for(int i=cur[u];i;i=nex[i])
		{
			cur[u]=i;
			int v=to[i];
			if(!w[i]||dep[v]!=dep[u]-1) continue;
			int res=dfs(v,min(w[i],in-out));
			w[i]-=res;
			w[i^1]+=res;
			out+=res;
			if(in==out) return out;
		}
		gap[dep[u]]--;
		if(!gap[dep[u]]) dep[s]=3*nm+3;
		dep[u]++;
		gap[dep[u]]++;
		return out;
	}
	void work()
	{
		bfs();
		while(dep[s]<3*nm+2)
		{
			memcpy(cur,first,sizeof(first));
			Maxflow+=dfs(s,1e9);
		}
	}
}
inline int id(int i,int j){return  (i-1)*m+j;}
int dx[5]={-1,0,1,0,0},dy[5]={0,-1,0,1,0};
int main()
{
	//1|nm|nm|nm|1
        //源点|每个组合"选文"|每个座位|每个组合"选理"|汇点
	n=read(),m=read(),nm=n*m;
	s=0,t=3*nm+1;
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		{
			int val=read();ans+=val;
			Add(s,id(i,j)+nm,val);//s -> i
		}
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		{
			int val=read();ans+=val;
			Add(id(i,j)+nm,t,val);//i -> t
		}
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		{
			int val=read();ans+=val;
			Add(s,id(i,j),val);//s -> x_i
		}
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		{
			int val=read();ans+=val;
			Add(id(i,j)+2*nm,t,val);//y_i -> t
			for(int k=0;k<5;++k)
			{
				int x=i+dx[k],y=j+dy[k];
				if(x<1||y<1||x>n||y>m) continue;
				Add(id(i,j),id(x,y)+nm,1e9);//x_i -> j
				Add(id(x,y)+nm,id(i,j)+2*nm,1e9);//j -> y_i
			}
		}
	}
	ISAP::work();
	printf("%d",ans-Maxflow);
	end();
	return 0;
}
posted @ 2021-07-07 20:15  After-glow  阅读(65)  评论(0)    收藏  举报