hdu-5652 并查集或者二分BFS

这道题是我当时打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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值