天天看點

POJ2706 Connect(并查集+模拟)

題目連結:http://poj.org/problem?id=2706

    這道題很水,真的很水。。。一看就知道不斷加點加邊,維護一個黑棋的并查集就可以了。

    唯一的難點在于判斷目前這一步與哪些點可以連接配接:

     觀察可發現,一條線隻有兩個連通方向:從左下連到右上,或從右上連到左下。

   如下圖:綠色的線為目前要判斷連通的點,黃色的線為與它在同一行(列)的三種截斷它的情況,其連通方向與綠色相反。再看左邊的那一個格子,綠線占據了上方的半個格子,是以上方的兩條紫色的線都可以截斷它,而下方隻有藍色的那條與它連通方向相反的線截斷它。同理,可推得右邊的格子。

POJ2706 Connect(并查集+模拟)

    再将這種情況拓展一下,我們就得到了目前這條線可以連通的條件為:

    ①與它所在的日字格子在同一行(列),即方向相同的三個日字格子中,沒有與它的連通方向相反的線存在。

    ②與它所在的日字格子互相垂直的兩個日字格子中,與它在同一方向的格子中沒有已經連通的線,與它在不同方向的格子中沒有與它的連通方向相反的線。(目前格子的方向即為線處于這個格子的哪個方位)

    于是,我們就可以暴力判斷了。

    每加一個點,我們就判斷它能否與已有的邊連通,若能,則連通。若為黑色點,則維護并查集。

    用一個v[x1][y1][x2][y2][2]來維護邊的連通情況:v[x1][y1][x2][y2][0]代表目前格子從左上到右下的連通情況,v[x1][y1][x2][y2][1]代表目前格子從左下到右上的連通情況。(注意:這裡的x1,y1,x2,y2指的是格子的列标與行标,而不是棋子所在的點的坐标)這樣就可以友善地判斷目前的線是否能夠連通。

    虛拟一個左端點與一個右端點,維護黑色棋子的連通情況,若加完點後左右相通,則黑棋獲勝,否則黑棋敗。(題目保證不會在最後一步前出現獲勝情況,并且各步棋都是合法的)

    判斷目前棋子與哪些點可以連通有很多種方法,本人用的最暴力的O(m²)的方法,空間什麼的也可以優化,因為一個日字格隻有一種連通方式,并且周圍的格子的連通也可以用它來表示,但因為在考試,是以本人沒有去研究什麼高大上的判斷與記錄連通的方法,簡簡單單地寫了四種情況,以後有時間再去研究一下高大上的判斷方法。我在網上看到很多題解都是用的BFS,于是就想發一個并查集吧,代碼如下:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

/*
  關鍵在于判斷目前的線是否可以連接配接。

  v[x1][y1][x1+1][y1][0]表示目前格子的左上到右下是否連接配接 
  v[x1][y1][x1+1][y1][1]表示目前格子的左下到右上是否連接配接
  
  v[x1][y1][x1][y1+1][0]表示目前格子的左上到右下是否連接配接 
  v[x1][y1][x1][y1+1][1]表示目前格子的左下到右上是否連接配接 
  
*/

int n,m,LEFT=0,RIGHT;

int v[25][25][25][25][2];

int x[250+10],y[250+10],fa[250+10];

int find(int x)//并查集不說了吧 
{
  if(x==fa[x])return x;
  return fa[x]=find(fa[x]);
}

void check(int fi,int x1,int y1,int li,int x2,int y2)
{
  if(x2<x1)//保證x1在左邊,友善判斷 
  {
  	int t=x1;x1=x2;x2=t;
  	t=y1;y1=y2;y2=t;
  }
  
  if(x1+1==x2)//日字格豎着的情況 
  {
  	if(y1+2==y2)
  	{
  	  //連通方式為1,與目前點在同一列的情況 
	  if(v[x1+1][y1+1][x1+1][y2][0])return;
	  if(v[x1+1][y2][x1+1][y2+1][0])return;
	  if(v[x1+1][y1][x1+1][y1+1][0])return;
	  
	  //與前一個格子垂直的情況 
	  if(v[x1][y1+1][x1+1][y1+1][1])return;
	  if(v[x1][y1+1][x1+1][y1+1][0])return;
	  if(v[x1+1][y1+1][x1+2][y1+1][0])return;
	  //與後一個格子垂直的情況 
	  if(v[x1+1][y1+2][x1+2][y1+2][1])return;
	  if(v[x1+1][y1+2][x1+2][y1+2][0])return;
	  if(v[x1][y1+2][x1+1][y1+2][0])return;
	  //可連通,連通 
	  v[x1+1][y1+1][x1+1][y1+2][1]=1;
	  v[x1+1][y1+2][x1+1][y1+1][1]=1;
	  //為黑色格子,維護并查集 
	  if(fi%2)
	  {
	  	fa[find(fi)]=find(li);
	  }
	  	
  	}
  	if(y1-2==y2)
  	{
  	  //連通方式為0,與目前點在同一列的情況 
  	  if(v[x1+1][y2+1][x1+1][y2+2][1])return;
	  if(v[x1+1][y2][x1+1][y2+1][1])return;
	  if(v[x1+1][y1][x1+1][y1+1][1])return;
	  
	  //與前一個格子垂直的情況
	  if(v[x1][y1][x1+1][y1][1])return;
	  if(v[x1][y1][x1+1][y1][0])return;
	  if(v[x1+1][y1][x1+2][y1][1])return;
	  //與後一個格子垂直的情況
	  if(v[x1+1][y1-1][x1+2][y1-1][1])return;
	  if(v[x1+1][y1-1][x1+2][y1-1][0])return;
	  if(v[x1][y1-1][x1+1][y1-1][1])return;
	  //可連通,連通
	  v[x1+1][y1-1][x1+1][y1][0]=1;
	  v[x1+1][y1][x1+1][y1-1][0]=1;
	  //為黑色格子,維護并查集
	  if(fi%2)
	  {
	  	fa[find(fi)]=find(li);
	  }	
  	}
  }
  else if(x1+2==x2)//日字格橫着的情況
  {
  	if(y1+1==y2)
  	{
  	  //連通方式為1,與目前點在同一行的情況 
  	  if(v[x1+1][y1+1][x1+2][y1+1][0])return;
	  if(v[x1][y1+1][x1+1][y1+1][0])return;
	  if(v[x2][y1+1][x2+1][y1+1][0])return;
	  
	  //與前一個格子垂直的情況
	  if(v[x1+1][y1][x1+1][y1+1][1])return;
	  if(v[x1+1][y1][x1+1][y1+1][0])return;
	  if(v[x1+1][y1+1][x1+1][y1+2][0])return;
	  //與後一個格子垂直的情況
	  if(v[x2][y2][x2][y2+1][1])return;
	  if(v[x2][y2][x2][y2+1][0])return;
	  if(v[x2][y2-1][x2][y2][0])return;
	  //可連通,連通
	  v[x1+1][y1+1][x1+2][y1+1][1]=1;
	  v[x1+2][y1+1][x1+1][y1+1][1]=1;
	  //為黑色格子,維護并查集
	  if(fi%2)
	  {
	  	fa[find(fi)]=find(li);
	  }	
  	}
  	if(y1-1==y2)
  	{
  	  //連通方式為0,與目前點在同一行的情況
  	  if(v[x1+1][y2+1][x1+2][y2+1][1])return;
	  if(v[x1][y2+1][x1+1][y2+1][1])return;
	  if(v[x2][y2+1][x2+1][y2+1][1])return;
	  
	  //與前一個格子垂直的情況
	  if(v[x1+1][y1][x1+1][y1+1][1])return;
	  if(v[x1+1][y1][x1+1][y1+1][0])return;
	  if(v[x1+1][y1-1][x1+1][y1][1])return;
	  //與後一個格子垂直的情況
	  if(v[x2][y2][x2][y2+1][1])return;
	  if(v[x2][y2][x2][y2+1][0])return;
	  if(v[x2][y2+1][x2][y2+2][1])return;
	  //可連通,連通
	  v[x1+1][y1][x1+2][y1][0]=1;
	  v[x1+2][y1][x1+1][y1][0]=1;
	  //為黑色格子,維護并查集
	  if(fi%2)
	  {
	  	fa[find(fi)]=find(li);
	  }	
  	}
  }
  
}

void work(int sx,int sy,int id)
{
  //這個是暴力枚舉的以前的點,其實可以開個map數組儲存一下 
  //這樣每次隻要枚舉八個方向就可以了 
	
  for(int i=1;i<id;i++)
  {
  	if(i%2==id%2)check(id,sx,sy,i,x[i],y[i]);//相同顔色才check
	  //傳遞編号,因為黑棋需要維護并查集 
  }
}

int main()
{
	
  while(scanf("%d%d",&n,&m)==2)
  {
  	if(n==0&&m==0)break;
  	
  	memset(v,0,sizeof(v));//初始化連通圖 
  	
  	for(int i=1;i<=m;i++)fa[i]=i;//初始化并查集 
  	fa[LEFT]=LEFT;RIGHT=m+1;fa[RIGHT]=RIGHT;
  	
  	for(int i=1;i<=m;i++)
  	{
  	  scanf("%d%d",&x[i],&y[i]);
  	  
  	  if(i%2)//黑棋,維護并查集 
  	  {
  	  	if(x[i]==0)fa[find(i)]=find(LEFT);
  	  	if(x[i]==n)fa[find(i)]=find(RIGHT);
  	  }
  	  
  	  work(x[i],y[i],i);//判連通 
  	  
  	}
  	
  	if(find(LEFT)==find(RIGHT))printf("yes\n");//左右連通,win 
  	else printf("no\n");//否則false 
  	
  }
	
  return 0;
}
           

速度挺不錯的:

POJ2706 Connect(并查集+模拟)

就這樣了。。。