bzoj1189: [HNOI2007]紧急疏散evacuate(二分+最大流+宽搜)

题目传送门
这道题真的是一道好题啊!!!!
表示做了两个小时。。
bzoj的数据是真的强(pi)。

一开始yy了个图结果发现错了。
上网看了看题解。按照构图敲了个代码。
错了!!!
听说被一组神数据卡掉了。
数据如下。
4 5
XXDXX
XX.XX
X…X
XXDXX
按照题解的方法跑出来答案是2但是手算是3。
很无语,只好自己yy了。。
所以想到了拆点。

建图是这样的:
st连接每个空地,流量为1(表示每个空地一开始有一个人)
每个人去找每一个门。
假设当前这个人距离某一个门为t。
如果t<=当前规定时间的话。很明显可以到达。所以连边。
那么他走过去就需要t的时间。
但是呢,因为同一时间不能有两个人通过同一个门。所以他有可能就是t+1的时间到的。
也有可能是t+2时间到的,还有其他的可能。
所以说我们把每一个门都拆成若干个点。
第一个点代表这个门在第1个时间点里到的人。
第二个点表示这个门在第2个时间点里到的人。
以此类推。
然后每一个门的点都去连接ed,容量为1。
表示的是这个门在这个时间点里只能有一个人通过(因为我已经拆点了嘛)
建好图跑最大流,最大流量等于在这个时间限制下最多能通过多少人。
如果全部都可以通过那么继续往小的二分。

代码实现:

#include
#include
#include
#include
#include
using namespace std;
struct node {
    int x,y,c,next,other;
}a[2100000];int len,last[6100000];
void ins(int x,int y,int c) {
    int k1,k2;
    len++;k1=len;
    a[len].x=x;a[len].y=y;a[len].c=c;
    a[len].next=last[x];last[x]=len;
    len++;k2=len;
    a[len].x=y;a[len].y=x;a[len].c=0;
    a[len].next=last[y];last[y]=len;
    a[k1].other=k2;
    a[k2].other=k1;
}
int st,ed,head,tail,list[210000],h[210000];
bool bfs() {
    memset(h,0,sizeof(h));h[st]=1;
    head=1;tail=2;list[1]=st;
    while(head!=tail) {
        int x=list[head];
        for(int k=last[x];k;k=a[k].next) {
            int y=a[k].y;
            if(h[y]==0&&a[k].c>0) {
                h[y]=h[x]+1;
                list[tail++]=y;
                if(tail==ed+1)
                    tail=1;
            }
        }
        head++;
        if(head==ed+1)
            head=1;
    }
    if(h[ed]==0)
        return false;
    return true;
}
int findflow(int x,int f) {
    if(x==ed)
        return f;
    int s=0,t;
    for(int k=last[x];k;k=a[k].next) {
        int y=a[k].y;
        if(a[k].c>0&&h[y]==h[x]+1&&sif(s==0)
        h[x]=0;
    return s;
}
int d[31][31][31][31]; //d[i][j][x][y]表示(i,j)到(x,y)的距离
struct dian {
    int x,y;
}llist[410];int n,m;
bool v[31][31],map[31][31];
int dx[5]={-1,0,1,0};
int dy[5]={0,1,0,-1};
bool pd(int x,int y) {
    if(x<1||y<1||x>n||y>m)
        return false;
    return true;
}
char ss[31][31];
void bfs(int stx,int sty) { //求出(stx,sty)这个位置到其他点的距离
    head=1;tail=2;llist[1].x=stx;llist[1].y=sty;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            d[stx][sty][i][j]=999999999;
    d[stx][sty][stx][sty]=0;
    memset(v,false,sizeof(v));v[stx][sty]=true;
    while(head!=tail) {
        dian tno=llist[head];
        int x=tno.x,y=tno.y;
        for(int i=0;i<=3;i++) {
            int tx=x+dx[i],ty=y+dy[i];
            if(d[stx][sty][tx][ty]>d[stx][sty][x][y]+1&&map[tx][ty]==true&&pd(tx,ty)==true) { //满足条件才可以走。
                d[stx][sty][tx][ty]=d[stx][sty][x][y]+1;
                if(v[tx][ty]==false&&ss[tx][ty]!='D') { //遇到一个门就要进去,不能再去别的地方了,所以门是不可以进入队列的。
                    v[tx][ty]=true;
                    llist[tail].x=tx;llist[tail++].y=ty;
                }
            }
        }
        head++;
    }
}

bool f[410][410];
struct Dian {
    int x,y;
}t[1100];
int main() {
    scanf("%d%d",&n,&m);
    memset(map,true,sizeof(v));
    int s=0,A=0;
    for(int i=1;i<=n;i++) {
        scanf("%s",ss[i]+1);
        for(int j=1;j<=m;j++) {
            if(ss[i][j]=='X')
                map[i][j]=false;
            if(ss[i][j]=='D')  //t数组存D的信息
                t[++s].x=i,t[s].y=j;
            if(ss[i][j]=='.')
                A++;  //A表示有多少块空地
        }
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(ss[i][j]=='.')
                bfs(i,j); //求每一块空地到别的地方的距离
    int l=1,r=400,mid,ans=-1;
    //点的编号我是这样排的。空地为1~n*m,门为n*m+1~n*m+mid*s
    //因为s表示有多少个门,每个门又拆成了若干个点(mid)
    while(l<=r) {
        mid=(l+r)/2;  //mid就是二分的时间限制
        len=0;memset(last,0,sizeof(last));
        st=n*m+mid*s+1,ed=st+1;   //每次的st和ed
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                if(ss[i][j]=='.')
                    ins(st,(i-1)*m+j,1); //st到每块空地容量为1
        int sss=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                if(ss[i][j]=='.') 
                    for(int k=1;k<=s;k++) {
                        Dian D=t[k];
                        int dd=d[i][j][D.x][D.y]; //距离等于时间。
                        while(dd<=mid) {
                            ins((i-1)*m+j,n*m+s*(dd-1)+k,1); //每一种可能都要连边
                            dd++;
                        }
                    }
        for(int i=n*m+1;i<=n*m+mid*s;i++) //每个门都去连ed
            ins(i,ed,1);

        int sum=0;
        while(bfs()==true)
            sum+=findflow(st,999999999);
        if(sum==A) { //如果最后通过的人数等于空地数那也就是合法了。
            r=mid-1;ans=mid;
        }
        else
            l=mid+1;
    }
    if(ans==-1)
        printf("impossible\n");
    else
        printf("%d\n",ans);
    return 0;
}

这道题真是好题,考验构图能力。
力荐!!!

你可能感兴趣的:(网络流,宽搜,二分,BZOJ)