1. 程式人生 > 實用技巧 >P5687 網格圖

P5687 網格圖

演算法原理

根據 \(\operatorname{Kruskal}\) 演算法的運算規則,每次總是會把當前邊權最小,且連線著本不連通的兩個點的邊選中。

而在這道題目中,位於同一行或列的邊的邊權大小一定是相同的,因此一定會接連選完這一行或列上所有可行的邊。

思考過程

選擇一行/列後,整個被選中的行/列上所有的點都位於同一個連通分量。

故選完至少一行和至少一列後,接下來構成生成樹的過程中,一定保證只存在一個包含兩個及以上節點的連通分量。

這是一個關鍵點,思考過程可以以這個點分成兩部分。

  1. 在選完至少一行和一列之前,保證接下來選擇的任何行/列,這一行/列上所有的邊都會被選擇。

  2. 而在選完至少一行和一列之後,對於即將被選擇的一行來說,這一行上尚未被連線進最小生成樹的點的個數 \(=\)

    總的列數 \(-\) 已被選擇的列數。而一條邊在構建最小生成樹時,只能將一個點與最小生成樹連線起來。因此選擇這一行能選擇的邊的條數,即等於這一行上尚未接入最小生成樹的點的個數。即總列數 \(-\) 已被選擇的列數。對於列來說同理。

因此總的演算法思路就明確了:

  1. 首先將所有的邊權值放到一起升序排列,注意記錄一下這個邊權是橫邊的邊權還是縱邊的邊權,開一個 pair 存很方便(自動按照第一關鍵字為索引進行升序排序)。一遍 sort 進行排序。開兩個變數 \(l\text{(line)}\)\(c\text{(column)}\) 儲存已經選擇的行/列數。

  2. 然後從小到大列舉排序後的邊權,在兩個統計變數都不為 \(0\)

    之前,任何選的行/列都將其所包含的所有邊全部加入最小生成樹。

  3. 在兩個統計變數都大於 \(0\) 之後,對於行來說,加入其包括的 \(m-c\) 條邊,即可保證這一行上所有的點全部加入最小生成樹,且不會影響到之後的選擇,故可以保證正確性。列同理。

Tips

  • 排序時把所有的邊放在了一起,因此記得把陣列開成兩倍。
  • 不開 long long 見祖宗

程式碼:

#include<bits/stdc++.h>

#define LL long long

using namespace std;

const int Maxe = 3e5 + 5;
pair<LL, bool> a[Maxe << 1];
int n, m;
LL ans = 0;
int main()
{
	scanf("%d%d", &n, &m);
	for(register int i = 1; i <= n; ++i)
	{
		int x;
		scanf("%d", &x);
		a[i] = make_pair(x, false);
	}
	for(register int i = n + 1; i <= n + m; ++i)
	{
		int x;
		scanf("%d", &x);
		a[i] = make_pair(x, true);
	}
	sort(a + 1, a + n + m + 1);
	int l = 0, c = 0;
	for(register int i = 1; i <= n + m; ++i)
	{
		if(!a[i].second)
		{
			if((!l)||(!c))
			{
				ans += (long long)(m - 1) * a[i].first;
			}
			else
			{
				ans += (long long)(m - c) * a[i].first;
			}
			l++;
		}
		else
		{
			if((!l)||(!c))
			{
				ans += (long long)(n - 1) * a[i].first;
			}
			else
			{
				ans += (long long)(n - l) * a[i].first;
			}
			c++;
		}
	}
	printf("%lld", ans);
	return 0;
}