YBTOJ:放置棋子(费用流)

题目描述

有一个n*n的棋盘,可以在上面放棋子。
有些格子不能放棋子,有些格子必须放棋子,剩下的格子随意。

要求放好棋子之后满足如下两条要求:

第 i 行和第 i 列的棋子数目必须一样多。
第 i 行的棋子数目不能超过总的棋子数目的 a/b。

求最多可以另外放多少个棋子(除掉必须放的)。如果无解输出 impossible。

解析

神仙题
这谁能想到是网络流啊… qwq
考虑正难则反,考虑舍弃哪些棋子
对于一个可以放弃的棋子 ( i , j ) (i,j) (i,j),就从 i 连一条向 j ,流量1,费用1的边。
然后对原点向每一行连一条流量是该行最多可以放置的棋子(包括必放和选放)
每列向汇点连同理的边
然后考虑放置棋子,因为条件1的限制,就从 i 行向 i 列连一条费用是0的边
这条边的流量就代表着第 i 行/列的放置的棋子数
这样就能使第i行和第i列选的棋子数相同
为了满足条件2,暴力枚举这条边的最大容量判合法即可

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=105;
const int M=2e6+100;
const int mod=998244353;
ll read(){
	ll x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();};
	while(isdigit(c)){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int n,m,s,t;
struct node{
	int to,nxt;
	ll cap,w;
}p[M<<1];
int fi[N],cnt,cur[N];
void addline(int x,int y,ll cap,ll w){
	p[++cnt]=(node){y,fi[x],cap,w};
	fi[x]=cnt;
	p[++cnt]=(node){x,fi[y],0,-w};
	fi[y]=cnt;
//	printf("  %d->%d cap=%lld w=%lld\n",x,y,cap,w);
}
ll flow,cost;
queue<int>q;
ll dis[N];
bool vis[N];
bool spfa(){
	memset(dis,0x3f,sizeof(dis));memset(vis,0,sizeof(vis));
	dis[s]=0;q.push(s);
	bool flag=0;
	while(!q.empty()){
		int now=q.front();q.pop();
		vis[now]=0;
		for(int i=cur[now]=fi[now];~i;i=p[i].nxt){
			int to=p[i].to;
			if(!p[i].cap) continue;
			if(to==t) flag=1;
			if(dis[to]>dis[now]+p[i].w){
				dis[to]=dis[now]+p[i].w;
				if(!vis[to]){
					q.push(to);vis[to]=1;
				}
			}
		}
	}
	return flag;
}
ll dfs(int x,ll lim){
	if(x==t||!lim){
		cost+=lim*dis[t];return lim;
	}
	vis[x]=1;
	ll res=0;
	for(int &i=cur[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(vis[to]||!p[i].cap||dis[to]!=dis[x]+p[i].w) continue;
		ll add=dfs(to,min(lim,p[i].cap));
		res+=add;lim-=add;
		p[i].cap-=add;p[i^1].cap+=add;
		if(!lim) break;
	}
	if(lim) dis[x]=-1;
	vis[x]=0;
	return res;
}
void dinic(){
	flow=0;cost=0;
	while(spfa()){
		while(ll tmp=dfs(s,2e18)){
			flow+=tmp;
			//printf("tmp=%d\n",tmp);
		}
	}
	return;
}
char mp[50][50];
int a,b;
bool flag;
int heng[55],su[55];
int tot,ans,res;
void work(int w){
	memset(fi,-1,sizeof(fi));cnt=-1;
	memset(heng,0,sizeof(heng));memset(su,0,sizeof(su));
	//printf("-----w=%d\n",w);
	s=2*n+1;t=s+1;tot=0;res=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(mp[i][j]=='/') continue;
			heng[i]++;su[j]++;tot++;
			if(mp[i][j]=='.') addline(i,j+n,1,1);
			else res++;
		}
	}
	for(int i=1;i<=n;i++){
		addline(s,i,heng[i],0);
		addline(i+n,t,su[i],0);
		addline(i,i+n,w,0);
	}
	dinic();
//	printf("flow=%d cost=%d tot=%d\n",flow,cost,tot);
	if(w*b<=(tot-cost)*a&&flow==tot){
		flag=1;ans=max(ans,tot-(int)cost);
	}
}
int main(){
	int o=0;
	while(1){
		flag=0;ans=0;
		n=read();a=read();b=read();
		if(n+a+b==0) break;
		for(int i=1;i<=n;i++) scanf(" %s",mp[i]+1);
		for(int i=0;i<=n;i++){
			work(i);
		}
		//work(1);
		o++;
		printf("Case %d: ",o);
		if(!flag) printf("impossible\n");
		else printf("%d\n",ans-res);
	}
    return 0;
}
/*

*/

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值