hdu-5652 并查集或者二分BFS
2016-04-03 13:30
423 查看
这道题是我当时打BC的时候做到的,一点思路没有好不好,谁知道竟然是并查集(并查集是我学的第一个数据结构,学得最好),真是一口老血。同时自己也反思一下,一段时间没有碰一种算法,那么这种算法的灵敏度是不是退步的很厉害。打比赛的时候不会做,自然窥了一下Acfun的屏,那些巨巨在几秒种之内想到了三种方法,真是厉害啊=、=(膜)。那么,言归正传,我这里也来介绍一下这三种做法。
TIP:这道题是一道连通性测试问题,但是求连通的是两条边上的所有点,直接上的话非常麻烦。这里有个小套路,自己创造两个点(印度和中国,沙漠和海洋),将两个点与两条边上的点连通,那么我们测试的就是我们自己创造的两个点的连通性,而不必麻烦的测试两条边上所有点的连通性了,是不是简单很多!
PS:我要是知道有这个套路的话,那么这就是一道纯的连通性问题,还是比较好想到并查集的。唉!可惜,我是后来知道的。嗯,学到了新套路就是好事!
做法一 :二分+BFS
分析:不过如果你不知道前面的套路的话,应该会想到使用BFS的(迷宫问题变种?)。但是Q的值有点大,一个一个BFS肯定会TLE,但是BFS的结果满足二分的111100000的形式,可以使用二分,用logN的复杂度寻找正好转换的那个点。唉,比赛的时候没有想到,而且这类题目做的少,朋友给了我一句名言:题目不会做,不如试试二分。
做法二:逆序 并查集
分析:这种做法是BC题解给出的做法,但是我觉得不是特别好,没有第三种做法好。
这次需要用到套路。从后往前看,将要变的山峰一次性先全部在图上表示出来(修改好),将图中可以走的格子(上下左右)作为边来看,这样方块图就是一个无向图,那么就是时时检测自己创造的两个点的连通性。按倒序将之前变化为山峰的点还原为平原,并且更新四周的边(注意如果在两条边上的话,还需要更新它和创造的两个点的连接),并且测试自己创造的两个点是不是在同一个集合里面(是否连通),找到第一连通的点,输入后一个的数值!
做法三: 正序 并查集
分析:感觉这种做法才是正统做法,最快,最好!
同样需要用到套路,但是这次是将沙漠和山峰作为点(不是中国和印度)。而且这次连接的不是平原的点,而是山峰的点。如果山峰的点在侧面上能够连通的话,这就表明两个国家之间已经不能连通了。但是有一个注意点,山峰之间四面八方都算是连通的(至于为什么自己画画图就知道了),所以和上一种做法不一样的地方就在这里。然后按正序将平原的点不断变成山峰,更新与之相连的山峰点(同样如果是在两条边上的话,那么就需要与自己创造的两点进行连接),不断测试沙漠与海洋的连通性,找到第一个连通的点,输出即可!
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; }
相关文章推荐
- 1. Two Sum
- connect函数
- Tsinsen A1104 砝码称重
- C#+一般处理程序+jquery.uploadify 上传XML文件并插入数据库中
- 网络常用英语术语精选
- [Guava]——加入约束
- Apache 启动时报错 No installed service named "Apache2.4"
- Swift延迟加载简单介绍一二
- java虚拟机HotSpot 的 GC 算法实现
- Unity3D研究院之两种方式播放游戏视频-转自雨松mono
- 117.完全背包
- linux yum命令详解
- Codeforces Round #346 (Div. 2) B (pair+vector的应用)
- PHPExcel导出
- Java NIO -AtomicInteger
- 例程1. LKB -- 慕司板IAP15
- 1.Mysql数据库的优化技术(1)
- HDOJ 1202 The calculation of GPA
- ViewPager
- achartengine/MPAndroidChart——图表实现之Java