[SCOI2007] 蜥蜴

前言

啊,现在看这题有亿点心梗,深刻记得当时把平面距离当成曼哈顿距离而非欧几里得距离的痛……

前置知识

题目描述

详情请看洛谷P2472 [SCOI2007] 蜥蜴

分析

算法的判断

石柱都不稳定,每次当蜥蜴跳跃时,所离开的石柱高度减 1(如果仍然落在地图内部,则到达的石柱高度不变)。

这看着是不是很像网络流中的流量流过后,这条路径受到的影响。而在这题中,流量是蜥蜴个数、容量是石柱高度

如果该石柱原来高度为 1,则蜥蜴离开后消失,以后其他蜥蜴不能落脚。

这和满流后删边的情况很像。

分析到这,基本上可以确定本题考的就是网络流中的最大流

建模

如果你想的是非常普通的网络流建模方法——依次枚举每个点,把它与能通过一次跳跃到达的点连边,容量为该点高度;若点上有蜥蜴,就与源点连边,容量为1;若其可以通过一次跳跃到图外,就与汇点连边,容量为INF——那我只能恭喜你爆零了。

接下来,让我们模拟一下跑最大流的过程(这个不可能不会吧),感受一下这个过程,你就会感觉到亿点不对劲。再造几个数据模拟一下,应该就能发现问题了——点与点之间的边的容量若为点的高度,就会导致一个点被经过的次数可能会超过点的高度,从而不满足题目限制。并且,这里最大的问题在于,我们无法对边的容量进行限定,从而限定点被经过的次数,因为我们无法预测每个点的边的经过情况。

那该怎么解决呢?这个时候就需要一个非常重要的做题技巧——拆点

在图上算法中,多利用的是边而非点,因而对于不好维护的点权,我们把它转变为指向它自己的边权就好了。但若是自己指向自己,那在网络流算法中是不会对答案造成影响的,因此我们需要换种方式——把一个点拆成两个状态,入点和出点,每个入点和出点之间的边的容量就是该点的高度。于是乎,这个问题就迎刃而解了。

相关代码如下:

for(int i=1;i<=n;i++){
	for(int j=1;j<=m;j++){
		cin>>c;
		g[i][j]=c-'0';
		int tmp=get(i,j);
		//拆点,重点!!!!!
		add(tmp,tmp+a,g[i][j]); //由入点指向出点,容量为高度 
		add(tmp+a,tmp,0);
	}
}

接下来只需要跑普通的最大流即可,这个很简单吧?

完整代码

#include <bits/stdc++.h>
using namespace std;
const int N=55,M=1005,INF=0x7f7f7f7f;
int n,m,d,s,t,g[N][N],h[M],cnt=-1,ch[M],dx[5]={0,1,0,-1},dy[5]={1,0,-1,0};
int dep[M],ans,q[M],f,r;
char c;
int a;
bool ins[M];
struct P{
	int ne,to,c,f;
}p[200005];
int get(int x,int y){//得到每个点对应的下标 
	return x*m+y;
}
void add(int f,int to,int c){//加边 
	p[++cnt].ne=h[f];
	p[cnt].to=to;
	p[cnt].c=c;
	h[f]=cnt;
}
void dfs(int x,int y,int u,int v){//利用dfs连边 
	ins[get(x,y)]=1;
	for(int i=0;i<4;i++){
		int xx=x+dx[i],yy=y+dy[i],f=get(u,v);
		if(ins[get(xx,yy)]||(xx-u)*(xx-u)+(yy-v)*(yy-v)>d*d){//是欧几里得距离!!!!! 
			continue;
		}
		if(xx<1||yy<1||xx>n||yy>m){
			add(f+a,t,INF);
			add(t,f+a,0);
			return;
		}
		if(g[xx][yy]){
			int tmp=get(xx,yy);
			add(f+a,tmp,INF);
			add(tmp,f+a,0);
		}
		dfs(xx,yy,u,v);
	}
}
bool bfs(){//利用bfs寻找从源点到汇点经过点数最少的路径,同时可以判断是否仍有解 
	memcpy(h,ch,sizeof(h));
	memset(dep,0,sizeof(dep));
	f=r=0;
	q[++r]=s;
	dep[s]=1;
	while(f!=r){
		int u=q[++f];
		for(int i=h[u];~i;i=p[i].ne){
			int v=p[i].to;
			if(dep[v]||p[i].c==p[i].f) continue;
			q[++r]=v;
			dep[v]=dep[u]+1;
		}
	} 
	return dep[t];
}
int dfs1(int x,int maxflow){// 跑最大流 
	if(x==t||maxflow==0) return maxflow;
	int curflow=0;
	for(int &i=h[x];~i;i=p[i].ne){
		int v=p[i].to;
		if(dep[v]!=dep[x]+1||p[i].c<=p[i].f) continue;
		int flow=dfs1(v,min(maxflow-curflow,p[i].c-p[i].f));
		curflow+=flow;
		p[i].f+=flow;
		p[i^1].f-=flow;
		if(curflow==maxflow) return curflow;
	}
	return curflow;
}
void dinic(){
	memcpy(ch,h,sizeof(ch));
	while(bfs()){
		ans-=dfs1(s,INF);
	}
}
int main(){
	memset(h,-1,sizeof(h));
	cin>>n>>m>>d;
	s=0,t=1;
	a=get(n,m);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>c;
			g[i][j]=c-'0';
			int tmp=get(i,j);
			//拆点,重点!!!!!
			add(tmp,tmp+a,g[i][j]); //由入点指向出点,容量为高度 
			add(tmp+a,tmp,0);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			memset(ins,0,sizeof(ins));
			dfs(i,j,i,j);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>c;
			if(c=='L'){
				ans++;
				add(s,get(i,j),1);
				add(get(i,j),s,0);
			}
		}
	}
	dinic();
	cout<<ans<<endl;
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值