算法竞赛入门经典 训练指南 之 图论(完全版--持续更新)
由于题目太多了,所以在更新过程中可能会出错,欢迎大家一起交流^_^。。。
第一题 题目:uva 11624 Fire
二维迷宫中,有一些着火点(可能不止一个),给出你的初始位置,人与着火点都是每秒钟向相邻的格子移动一格,问人能不能够在火势到达之前走到边界,如果能够,输出最短的时间
分析:对于着火点,可以预处理把所有的着火点都放进队列中,然后再用BFS处理出所有可燃烧的格子的最短到达时间。再用BFS来计算出人能够达到边界的最短间。。。
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int X = 1005; int dirx[] = {-1,1,0,0}; int diry[] = {0,0,-1,1}; int n; int m; bool use[X][X]; int tt[X][X]; char map[X][X]; struct node{ int x,y,step; node(int _x,int _y,int _step){ x = _x; y = _y; step = _step; } }; struct point{ int x,y,step; point(){} point(int _x,int _y,int _step){ x = _x; y = _y; step = _step; } }q[X*X]; bool out(int x,int y){ return x>=n||y>=m||x<0||y<0; } int bfs(int sx,int sy){ memset(use,false,sizeof(use)); int head = 0; int tail = 0; q[tail++] = point(sx,sy,0); while(head<tail){ point pre = q[head++]; int x = pre.x; int y = pre.y; if(x==0||y==0||x==n-1||y==m-1) return pre.step; pre.step ++; for(int i=0;i<4;i++){ int dx = dirx[i]+x; int dy = diry[i]+y; if(out(dx,dy)) continue; if(use[dx][dy]||tt[dx][dy]<=pre.step||map[dx][dy]=='#') continue; use[dx][dy] = true; q[tail++] = point(dx,dy,pre.step); } } return -1; } void init(){ memset(use,false,sizeof(use)); int head = 0; int tail = 0; for(int i=0;i<n;i++) for(int j=0;j<m;j++) if(map[i][j]=='F') q[tail++] = point(i,j,0); while(head<tail){ point pre = q[head++]; int x = pre.x; int y = pre.y; tt[x][y] = min(tt[x][y],pre.step ++); for(int i=0;i<4;i++){ int dx = dirx[i]+x; int dy = diry[i]+y; if(out(dx,dy)) continue; if(use[dx][dy]||map[dx][dy]=='#') continue; use[dx][dy] = true; q[tail++] = point(dx,dy,pre.step); } } } void solve(){ cin>>n; cin>>m; for(int i=0;i<n;i++) scanf("%s",map[i]); memset(tt,0x3f,sizeof(tt)); int sx,sy; sx = sy = 0; for(int i=0;i<n;i++) for(int j=0;j<m;j++) if(map[i][j]=='J'){ sx = i; sy = j; } init(); int ok = bfs(sx,sy); if(ok==-1) puts("IMPOSSIBLE"); else printf("%d\n",ok+1); } int main(){ freopen("sum.in","r",stdin); int ncase; cin>>ncase; while( ncase-- != 0 ) solve(); return 0; }
第二题 题目:uva 10047 The Monocycle
一辆独轮车等分分成了5个块,每个块分别有一种颜色,刚开始时独轮车的朝向为北,独轮车的底部为绿色,现在独轮车每秒可以先前移动一个格子,或者把方向移到向左或向右。现在问如何移动独轮车,使得独轮车到达终点且底部颜色为绿色的最短时间。(二维迷宫,若遇到障碍物不能向前移动)
分析:在原来的二维迷宫问题上增加二维,visit[x][y][direction][color],表示格子[x,y]中朝向为direction并以颜色color走过了的就不再重复走。其他的跟二维迷宫基本一样~~
#include <iostream> #include <cstring> #include <cstdio> #include <queue> using namespace std; const int X = 26; const int direction = 4; const int color = 5; bool use[X][X][direction][color]; int n,m; char map[X][X]; int sx,sy,ex,ey; int dirx[] = {-1,0,1,0}; int diry[] = {0,1,0,-1}; struct node{ int x,y; int d,c; int step; node(){} node(int _x,int _y,int _d,int _c,int _step):x(_x),y(_y),d(_d),c(_c),step(_step){} }; bool out(int x,int y){ return x<0||y<0||x>=n||y>=m; } int Abs(int x){ return max(x,-x); } void bfs(){ memset(use,false,sizeof(use)); queue<node> q; use[sx][sy][0][0] = true; q.push(node(sx,sy,0,0,0)); while(q.size()){ node pre = q.front(); q.pop(); int x = pre.x; int y = pre.y; int d = pre.d; int c = pre.c; if(x==ex&&y==ey&&pre.c==0){ printf("minimum time = %d sec\n",pre.step); return; } pre.step ++; for(int i=0;i<4;i++){ if(Abs(i-d)==2) continue; if(i==d){ int dx = dirx[i]+x; int dy = diry[i]+y; int dc = (c+1)%5; int dd = i; if(out(dx,dy)||map[dx][dy]=='#'||use[dx][dy][dd][dc]) continue; use[dx][dy][dd][dc] = true; q.push(node(dx,dy,dd,dc,pre.step)); } else{ if(use[x][y][i][c]) continue; use[x][y][i][c] = true; q.push(node(x,y,i,c,pre.step)); } } } puts("destination not reachable"); } int main(){ freopen("sum.in","r",stdin); int ncase = 0; while(cin>>n>>m,n||m){ if(ncase) puts(""); printf("Case #%d\n",++ncase); for(int i=0;i<n;i++){ scanf("%s",map[i]); for(int j=0;j<m;j++){ if(map[i][j]=='S'){ sx = i; sy = j; } else if(map[i][j]=='T'){ ex = i; ey = j; } } } bfs(); } return 0; }
第三题 题目: uva 10054 The Necklace
珠子由两部分组成,每个部分都有颜色,若相同颜色的两个珠子可以串连在一起,现在问能不能把所有的珠子串连在一起。若能够,给出串联的方式
分析:求欧拉回路,把每种颜色看做图上的一个顶点,然后对于每个珠子构造一条无向边,先判断是否有奇数度的顶点,若有的话,欧拉回路不存在,否则DFS一次即可。另外注意图中可能存在多条相同的边(我没测过),所以每次DFS的时候把边删掉一条即可
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int X = 60; const int n = 50; int map[X][X]; int id[X]; int m; void dfs(int x){ for(int y=1;y<=n;y++){ if(map[x][y]>0){ map[x][y] --; map[y][x] --; dfs(y); cout<<y<<" "<<x<<endl; } } } int main(){ freopen("sum.in","r",stdin); int ncase,x,y,cnt = 0; cin>>ncase; while(ncase--){ if(cnt) puts(""); printf("Case #%d\n",++cnt); memset(map,0,sizeof(map)); memset(id,0,sizeof(id)); scanf("%d",&m); while(m--){ scanf("%d%d",&x,&y); map[x][y] ++; map[y][x] ++; id[x] ++; id[y] ++; } bool ok = true; for(int i=1;i<=n;i++){ if(id[i]&1){ ok = false; puts("some beads may be lost"); break; } } if(!ok) continue; dfs(x); } return 0; }
第七题 题目:icpc 4287 Proving Equivalences
a能够推出b,b能够推出c,现在问增加多少个关系,使得所有的关系相互等价
分析:不难想到构造出有向图,然后求出图中的强连通分量,缩点之后求max(id,od)。id表示所有入度为0的顶点个数,od表示出度为0的个数,但是得要注意的是当连通块只有一个的时候,答案应该是0(缩点里的所有元素相互等价)
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn = 50005; const int maxm = 100005; int po[maxn],tol; int stack[maxn],dfn[maxn],low[maxn],father[maxn],bcnt,depth,top; bool instack[maxn]; int id[maxn],od[maxn]; int n,m; struct node{ int y,next; }edge[maxm]; void dfs(int x){ //递归实现tarjan算法 low[x] = dfn[x] = ++depth; instack[x] = true; stack[++top] = x; int y; for(int i=po[x];i;i=edge[i].next){ y = edge[i].y; if(!dfn[y]){ dfs(y); low[x] = min(low[x],low[y]); } else if(instack[y]) low[x] = min(low[x],dfn[y]); } if(low[x]==dfn[x]){ ++bcnt; do{ y = stack[top--]; instack[y] = false; father[y] = bcnt; }while(x!=y); } } void tarjan(){ memset(instack,false,sizeof(instack)); memset(dfn,0,sizeof(dfn)); top = bcnt = depth = 0; for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i); if(bcnt==1){ puts("0"); return; } memset(id,0,sizeof(id)); memset(od,0,sizeof(od)); for(int x=1;x<=n;x++) for(int j=po[x];j;j=edge[j].next){ int y = edge[j].y; if(father[x]!=father[y]){ od[father[x]] ++; id[father[y]] ++; } } int id_cnt = 0,od_cnt = 0; for(int i=1;i<=bcnt;i++){ if(!id[i]) id_cnt ++; if(!od[i]) od_cnt ++; } cout<<max(id_cnt,od_cnt)<<endl; } void add(int x,int y){ edge[++tol].y = y; edge[tol].next = po[x]; po[x] = tol; } int main(){ freopen("sum.in","r",stdin); int ncase; cin>>ncase; while(ncase--){ memset(po,0,sizeof(po)); tol = 0; int x,y; cin>>n>>m; while(m--){ scanf("%d%d",&x,&y); add(x,y); } tarjan(); } return 0; }
第八题 题目:uva 11324 The Largest Clique
从图中某点出发,求最远能够一次走过多少个节点
分析:
tarjan求gcc,然后构造出新图,新图是一个dag,对于dag上用dp求出最长路径即可。dp转移方程为dp[x] = size[x] + max(dp[y]); 缩点后有边x到y的边,记忆化搜索就行了,具体看实现代码
#include <iostream> #include <cstring> #include <cstdio> #include <vector> using namespace std; const int maxn = 1005; const int maxm = 50005; #define debug puts("here"); int dfn[maxn],low[maxn],stack[maxn],father[maxn],bcnt,top,depth; bool instack[maxn]; int po[maxn],tol,n,m; int id[maxn]; int dp[maxn]; int sum[maxn]; vector<int> vec[maxn]; struct node{ int y,next; }edge[maxm]; void add(int x,int y){ edge[++tol].y = y; edge[tol].next = po[x]; po[x] = tol; } void dfs(int x){ //递归实现tarjan算法 low[x] = dfn[x] = ++depth; instack[x] = true; stack[++top] = x; int y; for(int i=po[x];i;i=edge[i].next){ y = edge[i].y; if(!dfn[y]){ dfs(y); low[x] = min(low[x],low[y]); } else if(instack[y]) low[x] = min(low[x],dfn[y]); } if(low[x]==dfn[x]){ ++bcnt; do{ y = stack[top--]; instack[y] = false; father[y] = bcnt; }while(x!=y); } } void tarjan(){
memset(instack,false,sizeof(instack)); memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); top = bcnt = depth = 0; for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i); } int f(int x){ //记忆化方法求dag上的最长路径 if(dp[x]) return dp[x]; int ans = 0; for(int i=0;i<(int)vec[x].size();i++){ //从x的所有边出发,求出最大的路径 int y = vec[x][i]; ans = max(ans,f(y)); //转移方程 } dp[x] = ans+sum[x]; return dp[x]; } void dag(){ memset(id,0,sizeof(id)); memset(sum,0,sizeof(sum)); memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) vec[i].clear(); for(int x=1;x<=n;x++){ //构造新图 for(int j=po[x];j;j=edge[j].next){ int y = edge[j].y; if(father[x]!=father[y]){ vec[father[x]].push_back(father[y]); id[father[y]] ++; } } sum[father[x]] ++; //统计每个缩点后的该节点所包含的所有原图的节点数目 } int ans = 0; for(int i=1;i<=bcnt;i++) if(!id[i]) ans = max(f(i),ans); cout<<ans<<endl; } int main(){ freopen("sum.in","r",stdin); int ncase; cin>>ncase; while(ncase--){ cin>>n>>m; int x,y; memset(po,0,sizeof(po)); tol = 0; while(m--){ scanf("%d%d",&x,&y); add(x,y); } tarjan(); dag(); } return 0; }
。。。。。