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

来源:互联网 发布:php什么时候用双引号 编辑:程序博客网 时间:2024/04/29 10:40

题目传送门
这道题真的是一道好题啊!!!!
表示做了两个小时。。
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<cstdio>#include<cstring>#include<cstdlib>#include<iostream>#include<algorithm>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&&s<f) {            t=findflow(y,min(a[k].c,f-s));            s+=t;a[k].c-=t;a[a[k].other].c+=t;        }    }    if(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;}

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

阅读全文
0 0