QDU校内排位赛第三场 L 于公移山(网络流+最小割)

题目链接:https://pintia.cn/problem-sets/1330210570443206656/problems/1330211663055204353

题意:

思路:每个点要么是平原要么是高原,也就是把若干点分为两个集合,某些点之间还有冲突,典型的最小割模型。假设一个点为u,如果其是平原,将其与源点相连,流量为b(如果将s->u割去,要花费b,也就是将其变为高原的花费);如果其是高原,将其与汇点相连,流量为b(如果将u->t割去,要花费b,也就是将其变为平原的花费)。一个点(u)可能的冲突点(v)有其右方/下方的点,要将u与v相连,流量为a(如果将u->v割去,要花费a,也就是两点一个是平原,一个是高原);同理,要将v与u也相连,流量也为a。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2510;
const int M = 2*N*10;
const int inf = 0x3f3f3f3f;
struct node{
	int to,ca,nex;
}g[M];
int n,m,a,b;
int head[N],cnt,cur[N],s,t;
char mp[55][55];
void init(){
	cnt=s=0,t=n*m+1;
	for(int i=s;i<=t;i++)
		head[i]=-1;
}
int getid(int x,int y){
	return (x-1)*m+y;
}
void add(int u,int v,int w){
	g[cnt]=node{v,w,head[u]};
	head[u]=cnt++;
	g[cnt]=node{u,0,head[v]};
	head[v]=cnt++;
}
int dep[N];
bool bfs(){
	for(int i=s;i<=t;i++) dep[i]=0;
	dep[s]=1;
	queue<int> q;
	q.push(s);
	while(!q.empty()){
		int u = q.front();
		q.pop();
		if(u==t) return 1;
		for(int i=head[u];~i;i=g[i].nex){
			int v=g[i].to;
			if(!dep[v]&&g[i].ca>0){
				dep[v]=dep[u]+1;
				q.push(v);	
			}

		}
	}
	return 0;
}
int dfs(int u,int flow){
	if(flow==0||u==t)
		return flow;
	int nowf,ans=0;
	for(int& i=cur[u];~i;i=g[i].nex){
		int v=g[i].to;
		if(dep[v]==dep[u]+1&&g[i].ca>0){
			nowf=dfs(v,min(flow,g[i].ca));
			if(nowf){
				flow-=nowf;
				ans+=nowf;
				g[i].ca-=nowf;
				g[i^1].ca+=nowf;
			}
			if(!flow) break;		
		}
	
	}
	if(ans==0) dep[u]=0;
	return ans;
}
ll dicnic(){
	ll ans=0;
	int flow;
	
	while(bfs()){
		for(int i=s;i<=t;i++) cur[i]=head[i];	
		while(flow=dfs(s,inf)){
			ans+=flow;
		}		
	}
	return ans;
}
int main(void){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d%d",&n,&m,&a,&b);
		init();
		for(int i=1;i<=n;i++){
			scanf("%s",mp[i]+1);
			for(int j=1;j<=m;j++){
				int u=getid(i,j),v;
				if(mp[i][j]=='.') add(s,u,b);
				else add(u,t,b);
				if(i!=n){
					v=getid(i+1,j);
					add(u,v,a);
					add(v,u,a);
				}
				if(j!=m){
					v=getid(i,j+1);
					add(u,v,a);
					add(v,u,a);					
				}
			}
		}
		printf("%lld\n",dicnic());
	}	
	
	return 0;	
} 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值