ZJYYCOJ 好基友(貪心/並查集)

  • 題面描述
    每個班級總有幾對好基友,創意班也不例外,最典型的就是汪李弟弟組合。現在有N對基友隨機的坐在一排連續的座位上,好基友們總是希望能坐在一起,現在請你幫幫忙,計算一下最少要交換多少次(每兩個人交換一次位置算作1次),以便讓每對好基友都並肩坐在一起。
    人的編號爲 0 —— 2N - 1
    座位的編號爲 0 —— 2N - 1
    每對好基友按照編號順序來確定,(4,5)爲好基友,(8,9)爲好基友 , (0,1)爲好基友

  • 輸入
    樣例輸入由多組測試樣例組成,第一行輸入一個整數N(1 <= N <= 100000),代表有N對基友。第二行輸入2N個整數,代表每個位置初始上的人編號

  • 輸出
    輸出最少交換次數

  • 樣例輸入

  • 2
    0 2 1 3
    2
    3 2 0 1

  • 樣例輸出

  • 1
    0

  • 解題思路(偷懶使用官方解題思路啦)

  1. 貪心思路:
    根據我們的假設,可以制定按順序讓每張沙發上基友開心的策略。 對於每張沙發,找到沙發上第一個人的基友,如果不在同一個沙發 上,就把沙發上的第二人換成第一個人的基友。
    如果一個人的編號爲 x,那麼他的基友的編號爲 x ^ 1, ^ 在這裏 是異或操作。對於每張沙發上的第一個人 x = row[i],找到他們的 同伴所在的位置 row[j],將 row[j] 和 row[i + 1] 互相交換。
  2. 並查集思路:
    我們設想一下加入有兩對基友互相坐錯了位置,我們至多只需要換 一次。 如果三對基友相互坐錯了位置,A1+B2,B1+C2,C1+A2。他們三個之 間形成了一個環,我們只需要交換兩次。 如果四隊基友相互坐錯了位置,即這四對基友不與其他基友坐在一 起,A1+B2,B1+C2,C1+D2,D1+A2.他們四個之間形成了一個環,他們 只需要交換三次並且不用和其他基友交換,就可以將這四對基友交 換好,
    以此類推,其實就是假設 k 對基友形成一個環狀的錯誤鏈,我們只 需要交換 k - 1 次就可以將這 k 對基友的位置排好。 所以問題轉化成 N 對基友中,有幾個這樣的錯誤環。 我們可以使用並查集來處理,每次遍歷相鄰的兩個位置,如果他們 本來就是基友,他們處於大小爲 1 的錯誤環中,只需要交換 0 次。 如果不是基友,說明他們呢兩對處在同一個錯誤環中,我們將他們 合併(union),將所有的錯坐基友合併和後,答案就是基友對 - 環個數。
    這也說明,最差的情況就是所有 N 對基友都在一個環中,這時候我 們需要N - 1調換。
    最好情況每對基友已經坐好了,已經有 N 個大小爲 1 的環,這時候 我們需要N - N次調換。

貪心代碼

#include <bits/stdc++.h>

using namespace std;
int a[200005], pos[200005];

int main()
{
	int n;
	while(~scanf("%d", &n))
	{
		int sum = 0;  // 交換次數
		for(int i = 1; i <= 2 * n; i++)
		{
			scanf("%d", &a[i]);
			pos[a[i]] = i;  // 記錄每個同學的位置
		}
		for(int i = 1; i <= 2 * n; i += 2)
		{
			int temp = a[i] ^ 1;  // 第i個位置的基友
//			cout << temp << endl;
			if(a[i + 1] != temp)
			{
				a[pos[temp]] = a[i + 1];  // 序號互換
				a[i + 1] = temp;
				pos[a[pos[temp]]] = pos[temp];  // 位置互換
				pos[temp] = i + 1;
				sum++;
			}
		}
		printf("%d\n", sum);
	}
	return 0;
 } 

貪心這裏還是WA了兩次,是因爲只交換了兩個位置上的兩個序號但沒有交換這兩個序號的位置。

並查集代碼

#include <bits/stdc++.h>

using namespace std;
int fa[200005], sum[200005], a[200005];
int findx(int r)
{
   int temp, k = r;
   while(r != fa[r])
   	r = fa[r];
   //路徑壓縮
   while(k != r)
   {
   	temp = fa[k];
   	fa[k] = r;
   	k = temp;
   }
   return r;
}
void merge(int x, int y)
{
   int xx = findx(x), yy = findx(y);
   if(xx != yy)
   	fa[xx] = yy;
}

int main()
{
   ios::sync_with_stdio(false);
   int n, ans;
   while(cin >> n)
   {
   	ans = 0;
   	memset(sum, 0, sizeof(sum));
   	for(int i = 0; i < 2 * n; i++)
   		fa[i] = i;
   	for(int i = 0; i < 2 * n; i++)
   	{
   		cin >> a[i];
   		if(i % 2)
   		{
   			merge(i, i - 1);  // 合併基友
   			merge(a[i], a[i - 1]);  // 合併一個沙發上的同學
   		}
   	}
   	int temp;
   	for(int i = 0; i < 2 * n; i++)
   	{
   		temp = findx(i);
   		sum[temp]++;  // 將所有同學統計到同一個環的祖先上用於計算
   	}
   	for(int i = 0; i < 2 * n; i++)
   	{
   		if(sum[i])
   			ans += (sum[i] / 2 - 1);  // 同一個換需要交換(sum[i] / 2 - 1)次
   	}
   	cout << ans << endl;
   }
   return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章