【最大流Dinic】BZOJ1066(SCOI2007)[蜥蜴]题解

题目概述

给出一张图,图上每个点有1~3高度的石柱或没有石柱(高度为0)。有一些蜥蜴在图中,跳跃距离为D,一只蜥蜴离开一个石柱后,该石柱的高度就会减1,问最少有多少只蜥蜴无法跳到图外。

解题报告

按照题目里的描述,石柱高度就是能够经过的次数,那么这就是限流条件,所以我们可以比较自然的把这道题转化为最大流问题(而且数据这么小:P)。
首先由于石柱是点,所以需要拆点。然后加上超级源和超级汇,再建边搞来搞去刷Dinic就行了。
不过看起来效率很不正常?要相信Dinic的玄学……

示例程序

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=20,maxm=20,maxt=2*maxn*maxm,maxe=(8*8+3)*maxn*maxm,MAXINT=((1<<30)-1)*2+1;

int n,m,D,sum;
int E,lnk[maxt+5],dis[maxt+5],que[maxt+5],cur[maxt+5];
bool vis[maxt+5];
struct Edge
{
    int son,nxt,cap,flow;
    Edge(int a=0,int b=0,int c=0,int d=0) {son=a;nxt=b;cap=c;flow=d;}
};
Edge e[maxe+5];

char getrch() {char ch=getchar();while (('3'<ch||ch<'0')&&ch!='L'&&ch!='.') ch=getchar();return ch;}
int getid(int x,int y,int id) {return id*n*m+(x-1)*m+y;}
void Add(int x,int y,int z)
{
    e[E]=Edge(y,lnk[x],z,0);lnk[x]=E++;
    e[E]=Edge(x,lnk[y],0,0);lnk[y]=E++;
}
int sqr(int x) {return x*x;}
bool Bfs(int st,int gl)
{
    memset(vis,0,sizeof(vis));
    int Head=0,Tail=0;que[++Tail]=st;dis[st]=0;vis[st]=true;
    while (Head!=Tail)
    {
        int x=que[++Head];
        for (int j=lnk[x];~j;j=e[j].nxt)
            if (e[j].cap>e[j].flow&&!vis[e[j].son])
            {
                vis[e[j].son]=true;
                dis[e[j].son]=dis[x]+1;
                que[++Tail]=e[j].son;
            }
    }
    return vis[gl];
}
int Dfs(int x,int gl,int MIN)
{
    if (x==gl||!MIN) return MIN;
    int flow=0;
    for (int &j=cur[x],f;~j;j=e[j].nxt)
        if (dis[x]+1==dis[e[j].son]&&(f=Dfs(e[j].son,gl,min(MIN,e[j].cap-e[j].flow))))
        {
            e[j].flow+=f;e[j^1].flow-=f;
            MIN-=f;flow+=f;
            if (!MIN) break;
        }
    return flow;
}
int MAXflow(int st,int gl)
{
    int flow=0;
    while (Bfs(st,gl))
    {
        memcpy(cur,lnk,sizeof(cur));
        flow+=Dfs(st,gl,MAXINT);
    }
    return flow;
}
int main()
{
    freopen("program.in","r",stdin);
    freopen("program.out","w",stdout);
    scanf("%d%d%d",&n,&m,&D);
    memset(lnk,255,sizeof(lnk));
    for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
    {
        int x=getrch()-48;
        if (x) Add(getid(i,j,0),getid(i,j,1),x);
    } //点容量
    int st=0,gl=2*n*m+1;
    for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
        if (getrch()=='L')
            Add(st,getid(i,j,0),1),sum++; //超级源
    for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
    for (int x=max(i-D,1);x<=min(i+D,n);x++)
    for (int y=max(j-D,1);y<=min(j+D,m);y++)
        if ((i!=x||j!=y)&&sqr(i-x)+sqr(j-y)<=sqr(D))
            Add(getid(i,j,1),getid(x,y,0),MAXINT); //相邻建边
    for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
        if (min(min(i,j),min(n-i+1,m-j+1))<=D)
            Add(getid(i,j,1),gl,MAXINT); //超级汇
    printf("%d\n",sum-MAXflow(st,gl));
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值