前言
啊,现在看这题有亿点心梗,深刻记得当时把平面距离当成曼哈顿距离而非欧几里得距离的痛……
前置知识
题目描述
详情请看洛谷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;
}