您的位置:首页 > 其它

hdu-5652 并查集或者二分BFS

2016-04-03 13:30 423 查看
这道题是我当时打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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: