Luogu P5089 元素週期表 / Codeforces 1012B Chemical table 題解 [ 並查集 ] [ 二分圖 ] [ 圖論建模 ] [ 棋盤覆蓋問題 ]

KS_Fszha發表於2024-08-09

雙倍經驗:Luogu P5089 元素週期表CF1012B Chemical table:模擬賽搬的好題,有點厲害。賽時10min碼的假貪心拿了五十多分,贏。

並查集思路 1

對於此類棋盤整行整列覆蓋問題,有一個通用思路:把每一行和每一列看作一個點,那麼原本棋盤上的格子就可以看作是連線這些點的邊。例如一個點是 \((x,y)\) ,那麼我們就可以把行 \(x\) 代表的點與列 \(y\) 代表的點連一條邊。

這樣做的原因是如果確定了行與列,那麼我們就可以確定唯一的點。並且本題還是整行整列地進行覆蓋的,資料範圍較大,只能透過此類表達方式來把原來 \(10^{12}\) 級別的點,化為 \(10^{12}\) 級別的邊;剩下點的個數就為 \(2 \times 10^6\) 級別。

一些這種圖的性質:

  • 如果第 \(i\) 行與第 \(j\) 列聯通,可以當成格子 \((i,j)\) 處有一個點。
  • 本質上是把每個已有的格子,從橫縱兩個方向散開直線,這些直線只要形成交點,就是一個連通塊。在本題中這麼應用,是因為只要有 \(3\) 個點,我們就可以確定一個矩形。

接下來思考核聚變的過程:

對於點 \((x_1,y_1),(x_1,y_2),(x_2,y_1),(x_2,y_2)\) 組成一個矩形 ,我們先假設 \((x_2,y_2)\) 還沒有生成。

那麼連出的圖就長這樣:(圓表示 \(x\) ,方表示 \(y\) 。)

image

可以發現,點 \(x_2\) 與點 \(y_2\) 已經是聯通的了,並且由於上述的第一條性質:如果第 \(i\) 行與第 \(j\) 列聯通,可以當成格子 \((i,j)\) 處有一個點。此時的點已經自動被擴充了出來。

於是,我們只需要把 \(n+m\) 個所有的點都合併成一個連通塊,就可以了。

這個過程可以用並查集維護。

合併的次數就是連通塊的個數 \(-1\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,q,f[2000005],ans=0;
void init()
{
	for(int i=1;i<=n+m;i++)f[i]=i;
}
int findf(int x)
{
	if(f[x]!=x)f[x]=findf(f[x]);
	return f[x];
}
void combine(int x,int y)
{
	int fx=findf(x),fy=findf(y);
	f[fx]=fy;
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m>>q;
	init();
	while(q--)
	{
		int x,y;
		cin>>x>>y;
		combine(x,y+n);
	}
	for(int i=1;i<=n+m;i++)
	{
		ans+=(findf(i)==i);
	}
	cout<<ans-1;
	return 0;
}

並查集思路 2

某位金鉤爺的做法,有點複雜,但也好理解。這種做法是單純從本題的生成點的性質入手,而上一種做法就是單純從套路上入手。

首先我們畫個圖:

image

可以發現,如果相鄰兩行的同一列有棋子(藍色部分),那麼這兩行就完全同步狀態了。例如我們往第一行加上一些綠色點,那麼我們下面的紫色部分也會加上一些點。他們的狀態是完全同步的。

進一步擴充結論,就可以得到如果任意兩行的同一列有棋子,那麼這兩行就同步狀態了,所以他們就成連通塊了。

最終我們擴充完後,一定會形成一些沒有相同列的連通塊。

於是我們一開始就把行看成點,對有相同列的進行合併,統計連通塊個數(空行不能和空行算一個連通塊)。

然後特判一下有沒有空列,答案就是連通塊個數 \(-1\) 加上空列個數。

比上一種好理解一點。程式碼就不寫了,我懶。

二分圖思路

和並查集思路 1 的做法差不多,把二分圖分成上面行一部分,下面列一部分,然後照常合併。

然後遇到不連通的部分合並一下,統計一下就好了。基本和並查集一樣。

程式碼就不寫了,我懶。*2

相關文章