UVA1104 Chips Challenge(费用流)

神仙费用流题,理解了一下午,故写此篇题解以作纪念。

题意

有一个 \(N\times N\) 的棋盘,有些格子不能放棋子,有些格子必须放棋子,剩下的格子随意。要求放好棋子之后满足:

  1. \(i\) 行和第 \(i\) 列的棋子数相同。\((1\le i\le n)\)
  2. 任何一行的棋子数不能超过总的棋子数目的 \(\frac{A}{B}\)

求最多可以另外放多少个棋子。

做法

我们考虑先把整张图放满,再删去一些点。
首先可以想到的是用点 \(x_i\) 表示第 \(i\) 行,点 \(y_i\) 表示第 \(i\) 列,源点向所有 \(x_i\) 连边,所有 \(y_i\) 向汇点连边。
由于第二条限制与总棋子数有关,难以直接计算,可以枚举限制(即最多每行可以放的棋子数)后求最大的总棋子数判断合法性。
那么难点在于如何满足第一个限制。一个并不容易想到的思路是在最大流的基础上增加费用,用流量判断合法,用费用统计答案。
这里先给出建图方法,再逐一解释:
\(totx_i,toty_i\) 为第 \(i\) 行/列全部放满能放的棋子数,\(limit\) 为枚举的限制。

  • \(S\)\(x_i\) 连边,容量为 \(totx_i\),费用为 \(0\)\(y_i\)\(T\) 连边,容量为 \(toty_i\),费用为 \(0\)
  • \(x_i\)\(y_i\) 连边,容量为 \(limit\),费用为 \(0\)
  • 对于每个可以选择放不放的点 \((i,j)\)\(x_i\)\(y_j\) 连边,容量为 \(1\),费用为 \(1\)。若这条边有流,则代表移走点 \((i,j)\) 上的棋子。

对于该图跑最小费用最大流,最小费用即为最少删去的点数。
方案合法当且仅当所有直接与 \(S,T\) 相连的边满流,且剩下的棋子数 \(\ge \frac{B}{A}\times limit\)

接下来解释一下这样建图为什么能保证满足限制一。
考虑我们建的边的实际意义。
从点 \(x_i\) 流出的流量,流向费用为 \(1\) 的边则代表该点被删除,那么所有没被删除的点都流向了 \(y_i\)。那么 \(x_i\rightarrow y_i\) 这条边的流量即代表第 \(i\) 行保留的棋子数。
同理,\(x_i\rightarrow y_i\) 的流量也表示第 \(i\) 列保留的棋子数。所以第 \(i\) 行和第 \(i\) 列能够保证棋子数相等,且不会超过 \(limit\)

至于其他限制都在建图过程中直观体现,这里不再一一赘述。

AC code

#include<bits/stdc++.h>
#define il inline
using namespace std;
il int read()
{
	int xr=0,F=1;char cr=getchar();
	while(cr<'0'||cr>'9') {if(cr=='-') F=-1;cr=getchar();}
	while(cr>='0'&&cr<='9')
	    xr=(xr<<3)+(xr<<1)+(cr^48),cr=getchar();
	return xr*F;
}
const int N=5005,inf=1e9;
int n,a,b,s,t;
char mp[N][N];
struct edge{
	int nxt,to,w,c;
}e[N<<1];
int head[N],cnt=1;
void add(int u,int v,int w,int c){
	//cout<<u<<" "<<v<<" "<<w<<endl;
	e[++cnt]={head[u],v,w,c};head[u]=cnt;
	e[++cnt]={head[v],u,0,-c};head[v]=cnt;
}
int dis[N],now[N],in[N];
bool spfa()
{
	queue<int> q;
	for(int i=s;i<=t;i++) now[i]=head[i],dis[i]=inf;
	dis[s]=0,in[s]=1; q.push(s);
	while(!q.empty())
	{
		int u=q.front();in[u]=0,q.pop();
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to,w=e[i].w;
			if(w&&dis[v]>dis[u]+e[i].c)
			{
				dis[v]=dis[u]+e[i].c;
				if(!in[v]) in[v]=1,q.push(v);
			}
		}
	}
	return dis[t]!=inf; 
}
int dfs(int u,int sum)
{
	if(u==t) return sum;
	int res=0;in[u]=1;
	for(int &i=now[u];i&&sum;i=e[i].nxt)
	{
		int v=e[i].to,w=e[i].w;
		if(!w||in[v]||dis[v]!=dis[u]+e[i].c) continue;
		int k=dfs(v,min(sum,w));
		e[i].w-=k,e[i^1].w+=k;
		res+=k,sum-=k;
	}
	if(sum) dis[u]=inf;
	in[u]=0;return res;
}
il void clear()
{
	memset(e,0,sizeof(e)),cnt=1;
	memset(head,0,sizeof(head));
}
void build(int limit)
{
	for(int i=1;i<=n;i++)
	{
		add(i,i+n,limit,0);
		int tot=0;
		for(int j=1;j<=n;j++) if(mp[i][j]!='/') tot++;
		add(s,i,tot,0);tot=0;
		for(int j=1;j<=n;j++) if(mp[j][i]!='/') tot++;
		add(i+n,t,tot,0); 
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(mp[i][j]=='.') add(i,j+n,1,1);
		}
	}
}
int main()
{
	int cas=0;
	while("qwq")
	{
		cas++;
		n=read(),a=read(),b=read();
		if(!n) return 0; 
		s=0,t=2*n+1;int mx=-1,cntc=0,tot=0;
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				cin>>mp[i][j];
				if(mp[i][j]=='C') cntc++;
				if(mp[i][j]!='/') tot++;
			}
		}
		for(int i=0;i<=n;i++)
		{
			clear();
			build(i);
			int ans=0,flow=0;
			while(spfa())
			{
				int k=dfs(s,inf);
				ans+=dis[t]*k,flow+=k;
			}
			if(flow==tot&&i*b<=(tot-ans)*a) mx=max(mx,tot-ans-cntc);
		}
		printf("Case %d: ",cas);
		if(mx==-1) printf("impossible\n");
		else printf("%d\n",mx);
	}
	return 0;
}
posted @ 2023-01-30 15:51  樱雪喵  阅读(35)  评论(0编辑  收藏  举报