BZOJ 1189

这题是网络流最大流。
挺有趣的题,代码长度100行+,大小3000B+,Σ( ° △ °|||)︴
加强了数据后更是加大了代码难度,话说网上不少题解都不能过新数据,所以写一份(吐槽:tyvj的数据好弱,样例都WA,结果AC了。。。)。


怎么做呢?

二分是不可置否的,初始时l=0,r=sum(sum是’.’的数量),我们先二分答案mid(即时间),然后每次由门向四周遍历(BFS),将在可行时间内可以到门的’.’点记录下来,与之连流量为1的边,然后取源点S向’.’的点连流量为1的边,再由所有门向汇点连流量为mid的边,然后跑一遍最大流,若最大流等于’.’数则ans记录mid的值,然后在左边继续二分,不然就去右边二分。


好像很对,然后就

/**************************************************************
    Problem: 1189
    User: fantasticwtl
    Language: C++
    Result: 答案错误
****************************************************************/

停,这是错的,显然题目里清楚地说了:

每一秒钟只能有一个人移动到门的位置。

只是难想到反例,这有一组数据送给WA的童鞋:

Input
4 5
XXDXX
XX.XX
X…X
XXDXX

Output
3

懂了吧,这样会有两个人同时走出去的,怎么解决嘛。。。拆点大法好!
将门拆成mid个点(a1,a2,······,amid,代表时间为1秒,2秒,······,mid秒),每个点ai与汇点T连流量为1的边(完美解决了上述问题,每秒只有1人能过去),只有能在点的限制时间内到达的’.’点与ai连边,具体实现可能有所不同,主要看代码。

放一下代码,e,时间好慢(Dinic算法):

/**************************************************************
    Problem: 1189
    User: fantasticwtl
    Language: C++
    Result: 正确
    Time:4600 ms
    Memory:98560 kb
****************************************************************/

#include 
#include 
#include 
#include 
using namespace std;
const int fx[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int qcn,sum,d[5001],g[5001][5001],q[5001],qq[1001][2],n,m,cn,k,tot[21][21],ans,kk,tt,dd[21][21],can[1001][2];
char c,enter,s[21][21];
bool sf[1001],f[21][21];
void BL(int x,int y,int p,int z)
{
    memset(f,0,sizeof(f));memset(dd,0,sizeof(dd));memset(qq,0,sizeof(qq));memset(sf,0,sizeof(sf));
    int H=0,T=1;
    qq[1][0]=x; qq[1][1]=y; f[x][y]=1; dd[x][y]=0;
    while(Hint f1=qq[H][0],f2=qq[H][1];
        for(int i=0;i<=3;i++){
            int xx=f1+fx[i][0],yy=f2+fx[i][1];
            if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&f[xx][yy]==0&&sf[H]==0&&s[xx][yy]=='.'){
                f[xx][yy]=1;T++;qq[T][0]=xx;qq[T][1]=yy;dd[xx][yy]=dd[f1][f2]+1;
                if(dd[xx][yy]==z)sf[T]=1;//时间限制
                g[tot[xx][yy]][p]=1;//满足的连边
            }
        }
    }
}
bool jz ()
{
    memset(d,0,sizeof(d));
    d[1]=1;q[1]=1;
    int h=0,t=1;
    while(hfor(int i=1;i<=cn;i++)
         if(d[i]==0&&g[q[h]][i]){
             t++;d[i]=d[q[h]]+1;q[t]=i;
         }
        if(d[cn])return 1;
    }
    if(d[cn]==0)return 0;else return 1;
}
int find (int x,int y)
{
    if(x>=cn)return y;
    int a=0;
    for(int i=1;i<=cn;i++)
     if(d[i]==d[x]+1&&g[x][i]>0&&(a=find(i,min(y,g[x][i])))){g[x][i]-=a;g[i][x]+=a;return a;}
    return 0;
}
bool pd (int x)
{
    cn=qcn;
    memset(g,0,sizeof(g));
    for(int i=1;i<=n;i++)
     for(int j=1;j<=m;j++)
      if(s[i][j]=='.')g[1][tot[i][j]]=1;//源点与'.'点连边
    for(int i=1;i<=tt;i++)
     for(int j=1;j<=x;j++){//这里就是改进
        cn++;
        g[cn][qcn+tt*x+1]=1;
        BL(can[i][0],can[i][1],cn,j);
     }
    cn++;//建图部分
    int q=0,w=0;
    while(jz ())if(w=(find (1,100000000)))q+=w;//Dinic
    return(q==sum);//是否满足
}
int main ()
{
        scanf("%d%d",&n,&m);
        cn=1;
        for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                    if(s[i][j]=='.'){
                    cn++;
                    tot[i][j]=cn;
                    }
                    if(s[i][j]=='D'){
                        tt++;
                        can[tt][0]=i;
                        can[tt][1]=j;
                    }
            }
        }//输入部分
        qcn=cn;
        for(int i=1;i<=n;i++)
         for(int j=1;j<=m;j++)
            if(s[i][j]=='.')sum++;//统计'.'的个数
        int l=0,r=sum,ans=0;
        while(l<=r){//二分答案
            int mid=(l+r)>>1;
            if(pd (mid))ans=mid,r=mid-1;//判定答案
            else l=mid+1;
        }
        if(ans)printf("%d\n",ans);else printf("impossible\n");
}

你可能感兴趣的:(BZOJ)