hdu-5652 并查集或者二分BFS

来源:互联网 发布:网络男神 毒药 编辑:程序博客网 时间:2024/06/04 18:28

这道题是我当时打BC的时候做到的,一点思路没有好不好,谁知道竟然是并查集(并查集是我学的第一个数据结构,学得最好),真是一口老血。同时自己也反思一下,一段时间没有碰一种算法,那么这种算法的灵敏度是不是退步的很厉害。打比赛的时候不会做,自然窥了一下Acfun的屏,那些巨巨在几秒种之内想到了三种方法,真是厉害啊=、=(膜)。那么,言归正传,我这里也来介绍一下这三种做法。


TIP:这道题是一道连通性测试问题,但是求连通的是两条边上的所有点,直接上的话非常麻烦。这里有个小套路,自己创造两个点(印度和中国,沙漠和海洋),将两个点与两条边上的点连通,那么我们测试的就是我们自己创造的两个点的连通性,而不必麻烦的测试两条边上所有点的连通性了,是不是简单很多!

PS:我要是知道有这个套路的话,那么这就是一道纯的连通性问题,还是比较好想到并查集的。唉!可惜,我是后来知道的。嗯,学到了新套路就是好事!


做法一 :二分+BFS

分析:不过如果你不知道前面的套路的话,应该会想到使用BFS的(迷宫问题变种?)。但是Q的值有点大,一个一个BFS肯定会TLE,但是BFS的结果满足二分的111100000的形式,可以使用二分,用logN的复杂度寻找正好转换的那个点。唉,比赛的时候没有想到,而且这类题目做的少,朋友给了我一句名言:题目不会做,不如试试二分。

#include<set>#include<map>#include<cmath>#include<stack>#include<queue>#include<vector>#include<iostream>#include<cstdio>#include<cstring>#include<string>#include<algorithm>#include<cctype>#define maxn 500+5#define clr(x,y) memset(x,y,sizeof(x))using namespace std;const int inf = 0x3f3f3f3f;typedef long long ll;const double pi = acos( -1 );const ll mod = 1e9+7;int n,m;char gra[maxn][maxn];char tmp[maxn][maxn];int vis[maxn][maxn];int qry[(maxn)*(maxn)][2];int dir[4][2]={0,1,0,-1,1,0,-1,0};bool bfs(int q){    for(int i=0;i<n;i++)        for(int j=0;j<m;j++)            tmp[i][j]=gra[i][j];    for(int i=0;i<=q;i++)        tmp[qry[i][0]][qry[i][1]]='1';    clr(vis,0);    queue<int>que;    for(int i=0;i<m;i++)        if(tmp[0][i]=='0')        {            vis[0][i]=1;            que.push(i);        }    while(!que.empty())    {        int a=que.front();        que.pop();        int b=a%1000;        a/=1000;        if(a==n-1)return true;        for(int i=0;i<4;i++)        {            int ta=a+dir[i][0];            int tb=b+dir[i][1];            if(ta>=0&&ta<n&&tb>=0&&tb<m&&tmp[ta][tb]=='0'&&vis[ta][tb]==0)            {                vis[ta][tb]=1;                que.push(ta*1000+tb);            }        }    }    return false;}int main(){    //freopen("d:\\acm\\in.in","r",stdin);    int t;    scanf("%d",&t);    while(t--)    {        scanf("%d %d",&n,&m);        for(int i=0;i<n;i++)            scanf("%s",gra[i]);        int q;        scanf("%d",&q);        for(int i=0;i<q;i++)            scanf("%d %d",&qry[i][0],&qry[i][1]);        int l=0,r=q;        int mid;        int ans=-1;        while(l<=r)        {            mid=(l+r)>>1;            if(!bfs(mid))            {                ans=mid;                r=mid-1;            }            else l=mid+1;        }        printf("%d\n",ans+1);    }    return 0;}




做法二:逆序 并查集

分析:这种做法是BC题解给出的做法,但是我觉得不是特别好,没有第三种做法好。

这次需要用到套路。从后往前看,将要变的山峰一次性先全部在图上表示出来(修改好),将图中可以走的格子(上下左右)作为边来看,这样方块图就是一个无向图,那么就是时时检测自己创造的两个点的连通性。按倒序将之前变化为山峰的点还原为平原,并且更新四周的边(注意如果在两条边上的话,还需要更新它和创造的两个点的连接),并且测试自己创造的两个点是不是在同一个集合里面(是否连通),找到第一连通的点,输入后一个的数值!

<span style="font-size:18px;">#include<set>#include<map>#include<cmath>#include<stack>#include<queue>#include<vector>#include<iostream>#include<cstdio>#include<cstring>#include<string>#include<algorithm>#include<cctype>#define maxn 500+5#define clr(x,y) memset(x,y,sizeof(x))using namespace std;const int inf = 0x3f3f3f3f;typedef long long ll;const double pi = acos( -1 );const ll mod = 1e9+7;int n,m,S,T;char gra[maxn][maxn];int pre[(maxn)*(maxn)];int qry[(maxn)*(maxn)][2];int dir[4][2]={0,1,0,-1,1,0,-1,0};int find(int x){    return pre[x]==x?x:pre[x]=find(pre[x]);}void un(int a,int b){    pre[find(a)]=find(b);}int code(int a,int b){    return a*m+b;}bool in(int a,int b){    if(a>=0&&a<n&&b>=0&&b<m)return true;    return false;}void charu(int a,int b){    if(gra[a][b]=='1')return;    for(int i=0;i<4;i++)    {        int ta=a+dir[i][0];        int tb=b+dir[i][1];        if(in(ta,tb)&&gra[ta][tb]=='0')            un(code(a,b),code(ta,tb));    }    if(a==0)un(code(a,b),S);    if(a==n-1)un(code(a,b),T);}int main(){    //freopen("d:\\acm\\in.in","r",stdin);    int t;    scanf("%d",&t);    while(t--)    {        scanf("%d %d",&n,&m);        for(int i=0;i<n;i++)            scanf("%s",gra[i]);        S=n*m,T=S+1;        for(int i=0;i<=T;i++)            pre[i]=i;        int q;        scanf("%d",&q);        for(int i=0;i<q;i++)        {            scanf("%d %d",&qry[i][0],&qry[i][1]);            gra[qry[i][0]][qry[i][1]]='1';        }        for(int i=0;i<n;i++)            for(int j=0;j<m;j++)                charu(i,j);        if(find(S)==find(T))        {            puts("-1");            continue;        }        for(int i=q-1;i>=0;i--)        {            gra[qry[i][0]][qry[i][1]]='0';            charu(qry[i][0],qry[i][1]);            if(find(S)==find(T))            {                printf("%d\n",i+1);                break;            }        }    }    return 0;}</span>




做法三: 正序 并查集

分析:感觉这种做法才是正统做法,最快,最好!

同样需要用到套路,但是这次是将沙漠和山峰作为点(不是中国和印度)。而且这次连接的不是平原的点,而是山峰的点。如果山峰的点在侧面上能够连通的话,这就表明两个国家之间已经不能连通了。但是有一个注意点,山峰之间四面八方都算是连通的(至于为什么自己画画图就知道了),所以和上一种做法不一样的地方就在这里。然后按正序将平原的点不断变成山峰,更新与之相连的山峰点(同样如果是在两条边上的话,那么就需要与自己创造的两点进行连接),不断测试沙漠与海洋的连通性,找到第一个连通的点,输出即可!

#include<set>#include<map>#include<cmath>#include<stack>#include<queue>#include<vector>#include<iostream>#include<cstdio>#include<cstring>#include<string>#include<algorithm>#include<cctype>#define maxn 500+5#define clr(x,y) memset(x,y,sizeof(x))using namespace std;typedef long long ll;const int inf = 0x3f3f3f3f;const double pi = acos( -1 );const ll mod = 1e9+7;const double eps = 1e-10;int S,T;int n,m;int pre[(maxn)*(maxn)];char gra[maxn][maxn];int dir[8][2]={0,1,0,-1,1,0,-1,0,1,-1,1,1,-1,1,-1,-1};int find(int x){    return pre[x]==x?x:pre[x]=find(pre[x]);}void un(int a,int b){    pre[find(a)]=find(b);}bool bing(int a,int b){    if(find(a)==find(b))return true;    return false;}bool in(int a,int b){    if(a>=0&&a<n&&b>=0&&b<m)return true;    return false;}int code(int a,int b){    return a*m+b;}void add(int a,int b){    for(int i=0;i<8;i++)    {        int ta=a+dir[i][0];        int tb=b+dir[i][1];        if(in(ta,tb)&&gra[ta][tb]=='1')            un(code(ta,tb),code(a,b));    }    if(b==0)un(code(a,b),S);    if(b==m-1)un(code(a,b),T);}int main(){    //freopen("d:\\acm\\in.in","r",stdin);    int t;    scanf("%d",&t);    while(t--)    {        scanf("%d %d",&n,&m);        S=n*m,T=S+1;        for(int i=0;i<=T;i++)            pre[i]=i;        for(int i=0;i<n;i++)            scanf("%s",gra[i]);        for(int i=0;i<n;i++)            for(int j=0;j<m;j++)                if(gra[i][j]=='1')                    add(i,j);        int ans=-1;        int q;        scanf("%d",&q);        for(int i=1;i<=q;i++)        {            int x,y;            scanf("%d %d",&x,&y);            gra[x][y]='1';            add(x,y);            if(ans==-1&&bing(S,T))                ans=i;        }        printf("%d\n",ans);    }    return 0;}


0 0