【负权图网络流】JZOJ6169.【GDSOI 2019 day1】棋盘

20 篇文章 0 订阅
9 篇文章 0 订阅

Description

  • 放假了无聊的小 W 和小 S 在玩游戏。游戏规则是这样的:

  • 现在有个 N × M 的空棋盘,要往格子里摆棋子。每个格子可以摆一个棋子,但是每行每列都有各自的棋子数目的上限和下限。

  • 小 S 提出了个他喜欢的摆放方案,不一定合法,但不会超过上限限制。

  • 小 W 需要找到一个合法的方案,使得跟小 S 的方案状态不同的格子数目最少。一个格子如果在一个方案中摆了棋子,但在另一个方案中没摆棋子,则视为状态不同。两个方案摆放的棋子数目可以不相同。

  • n , m < = 50 n,m<=50 n,m<=50

Solution

  • 非常裸的上下界费用流的问题,注意要优化连边,并不需要每一个格子开一个点,只需要每一行、每一列一个点就好。
  • 但是这题有负权边,会出现负环。

负权图网络流

  • 一种简便的方法是先钦定所有的负权边满流(加入它们的反向边)。那么原图中就只有正权边了。然后需要平衡流量。
  • 建立超级源ss、超级汇tt,类似上下界网络流一样平衡——ss->y,x->tt。
  • 然后跑在新图上最小费用最大流即可消除负环,相当于是平衡流量,从超级源连出去的流量来代替负权边的对应的入度,连向超级汇对应负权边的出度。
  • 在残量网络上再跑一跑。

关于这题

  • 简单粗暴的方法是在原来上下界的超级源和超级汇外再开一个超超级源和超超级汇。消掉负环后再回到原来的图上跑。
  • 但是因为这题的负权边与ss和tt无关,可以把ss当做sss,tt当做ttt。
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 105
#define maxm 200005
using namespace std;

int n,m,kk,i,j,k,ss,tt,s,t,tot,a[maxn][maxn];
int em,e[maxm],nx[maxm],ls[maxn],cost[maxm],ec[maxm];
int ans;

void link(int x,int y,int v,int c){
	if (c<0){
		link(y,x,v,-c);
		link(ss,y,v,0);
		link(x,tt,v,0);
		ans+=c*v;
	} else {
		em++,e[em]=y; nx[em]=ls[x]; ls[x]=em; ec[em]=v; cost[em]=c;
		em++,e[em]=x; nx[em]=ls[y]; ls[y]=em; ec[em]=0; cost[em]=-c;
	}
}

void insert(int x,int y,int L,int R,int c){
	if (L) link(ss,y,L,c),link(x,tt,L,0);
	if (R-L) link(x,y,R-L,c);
}

void build(){
	em=1,s=++tot,t=++tot,ss=++tot,tt=++tot;
	for(i=1;i<=n;i++){
		scanf("%d%d",&j,&k);
		insert(s,i,j,k,0);
	}
	for(i=1;i<=m;i++){
		scanf("%d%d",&j,&k);
		insert(n+i,t,j,k,0);
	}
	for(i=1;i<=kk;i++) scanf("%d%d",&j,&k),a[j][k]=1;
	for(i=1;i<=n;i++) for(j=1;j<=m;j++)
		insert(i,n+j,0,1,(a[i][j])?-1:1);
	insert(t,s,0,2e9,0);
}

int h,w,d[maxm],dis[maxn],fai[maxn],fa[maxn],vis[maxn];
int SPFA(){
	memset(dis,127,sizeof(dis));
	memset(vis,0,sizeof(vis));
	h=0,w=1,d[1]=ss,dis[ss]=0,vis[ss]=1;
	while (h<w){
		int x=d[++h];
		for(i=ls[x];i;i=nx[i]) if (ec[i]&&dis[x]+cost[i]<dis[e[i]]){
			dis[e[i]]=dis[x]+cost[i];
			fa[e[i]]=x,fai[e[i]]=i;
			if (!vis[e[i]]) 
				d[++w]=e[i],vis[e[i]]=1;
		}
		vis[x]=0;
	}
	return dis[tt]<2e9;
}

void maxflow(){
	while (SPFA()){
		ans+=dis[tt];
		for(int x=tt;x!=ss;x=fa[x])
			ec[fai[x]]--,ec[fai[x]^1]++;
	}
}

int main(){
	freopen("chess.in","r",stdin);
	freopen("chess.out","w",stdout);
//	freopen("ceshi.in","r",stdin);
//	freopen("ceshi.out","w",stdout);
	scanf("%d%d%d",&n,&m,&kk),tot=n+m,ans=kk;
	build();
	maxflow();
	printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值