P2472 [SCOI2007] 蜥蜴
题目链接:link
题面:
P2472 [SCOI2007] 蜥蜴
题目描述
在一个 \(r\) 行 \(c\) 列的网格地图中有一些高度不同的石柱,第 \(i\) 行 \(j\) 列的石柱高度为 \(h_{i,j}\)。
一些石柱上站着一些蜥蜴,你的任务是让尽量多的蜥蜴逃到边界外。
每行每列中相邻石柱的距离为 \(1\),蜥蜴的跳跃距离是 \(d\),即蜥蜴可以跳到平面距离不超过 \(d\) 的任何一个石柱上。
石柱都不稳定,每次当蜥蜴跳跃时,所离开的石柱高度减 \(1\)(如果仍然落在地图内部,则到达的石柱高度不变)。
如果该石柱原来高度为 \(1\),则蜥蜴离开后消失,以后其他蜥蜴不能落脚。
任何时刻不能有两只蜥蜴在同一个石柱上。
输入格式
输入格式
输入第一行为三个整数 \(r,c,d\),即地图的规模与最大跳跃距离。
接下来 \(r\) 行每行 \(c\) 个数字为石柱的初始状态,\(0\) 表示没有石柱,否则表示石柱的初始高度 \(h_{i,j}\)。
接下来 \(r\) 行每行 \(c\) 个字符为蜥蜴位置,L 表示蜥蜴,. 表示没有蜥蜴。
输出格式
输出仅一行,包含一个整数,即无法逃离的蜥蜴总数的最小值。
输入输出样例 #1
输入 #1
5 8 2
00000000
02000000
00321100
02000000
00000000
........
........
..LLLL..
........
........
输出 #1
1
说明/提示
对于 \(100\%\) 的数据满足:\(1\le r,c\le20\),\(1\le d\le 4\),\(1\le h\le 3\)。
思路:
把一个石柱分为顶点和底点两个点,两点间的容量为石柱的高度。设置一个超级源点和超级汇点。接下来考虑连边:
-
超级源点向每个有蜥蜴石柱的底点连一个容量为1的边。
-
能逃出的石柱的顶点向超级汇点连一条容量为inf的边。
-
在距离小于d的两个石柱连一条容量为inf的边,连接其中一个石柱的底点和另一个石柱的顶点。
tips:所有反边val值为 0 !
其他细节见代码注释叭 ~
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=10005,inf=1e9;
int m,n,d,k,cnt=1,tot,s,t,ans,head[N],dis[N],cur[N],h[1005][1005],x[N],y[N],id[1005][1005];
char ch[205];queue<int> q;
struct node{int to,nxt,val;}e[N<<1];
inline void add(int x,int y,int z){
e[++cnt].to=y;
e[cnt].val=z;
e[cnt].nxt=head[x];
head[x]=cnt;
}//建边就不用多说了吧
inline bool bfs(int s,int t){
memset(dis,0x3f,sizeof(dis));
while(!q.empty()) q.pop();//清空
for(int i=0;i<=2*k+1;i++) cur[i]=head[i];//当前弧优化
dis[s]=0;
q.push(s);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(dis[y]>inf&&e[i].val){//未被遍历过且有剩余流量
dis[y]=dis[x]+1;//更新距离
q.push(y);
}
}
}
if(dis[t]<inf) return true;//能到汇点 即还有剩余网络
else return false;
}
inline int dfs(int x,int t,int limit){
if(!limit||x==t) return limit;//到达汇点或没有剩余流量
int flow=0,f;
for(int i=cur[x];i;i=e[i].nxt){
cur[x]=i;
int y=e[i].to;//当前弧优化
if(dis[y]==dis[x]+1&&e[i].val&&(f=dfs(y,t,min(limit,e[i].val)))){//流量最小值
flow+=f;//最大流增加
limit-=f;//剩余流减少
e[i].val-=f;//此条边的流量减少
e[i^1].val+=f;//反向边的流量增加
if(!limit) break;//没有剩余流量
}
}
return flow;//返回答案
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>m>>d;
for(int i=1;i<=n;i++){
cin>>(ch+1);
for(int j=1;j<=m;j++){
h[i][j]=ch[j]-48;//记录高度
if(h[i][j]){
k++;//计录石柱数量
x[k]=i;y[k]=j;//记录石柱做标
id[i][j]=k;//石柱编号
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(h[i][j]&&(i<=d||j<=d||i+d>n||j+d>m)){//能跳出去
add(id[i][j]+k,2*k+1,inf);add(2*k+1,id[i][j]+k,0);//向2 ×k + 1(汇点)连边
}
}
}
for(int i=1;i<=n;i++){
cin>>(ch+1);
for(int j=1;j<=m;j++){
if(ch[j]=='L'){//有蜥蜴
tot++;//记录蜥蜴数量
add(0,id[i][j],1);add(id[i][j],0,0);//连超级源点(0)
}
}
}
for(int i=1;i<=k;i++){
add(i,i+k,h[x[i]][y[i]]);//石柱的顶点与底点相连
add(i+k,i,0);
}
for(int i=1;i<=k;i++){
for(int j=1;j<=k;j++){
if(i==j) continue;
if((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])<=d*d){//距离小于d的石柱相连
add(i+k,j,inf);add(j,i+k,0);//顶点连底点
}
}
}
s=0;t=2*k+1;//超级源点 and 超级汇点
while(bfs(s,t)) ans+=dfs(s,t,inf);//据说是网络流板子
cout<<tot-ans<<'\n';//总数减去能逃走的数量就是答案
return 0;
}