1. 程式人生 > 其它 >題解 P5687 [CSP-S2019 江西] 網格圖

題解 P5687 [CSP-S2019 江西] 網格圖

P5687 [CSP-S2019 江西] 網格圖

考對 Kruskal 的理解。

第一想法肯定是暴力建邊然後跑 Kruskal ,寫出來就有 \(64\) pts。

但是看到最後一檔資料 \(3\le n,m,\le 3\times 10^5\) ,並且題目中每一行每一列的邊權值一樣,正解一定不想讓我們去遍歷每個點每條邊,而是想讓我們根據行和列的性質一次處理一行/列的邊之類的。

我們考慮 Kruskal 實際上是一種貪心:每次儘量取最小的邊,然後保證圖的聯通。

先對權值排序,然後我們將最小的行和列加進來,這兩組邊一定會選,易證沒有最優方案優於不選這兩組邊。

那麼接下來我們繼續一整列一整行的加入,就會遇到另一個問題:判環。

考慮環出現的情況,一個環出現的時候,一定是選了一行邊穿過了多列已經選擇的列,或是選擇一列穿過了多行已選擇行。

eg:

綠色是之前選中的邊,紅色列的邊選中之後就會形成一個環。

如何避免這種情況又儘量多加入這列的邊?其實我們每次加邊的時候將連線到之前就加入行或列的邊刪去一條即可。由於我們不在乎是哪條邊刪去了,只在乎有沒有邊刪去與權值和的變化,所以我們每次記錄選了多少行 / 列,然後每次選行列的時候減去對應數量的邊即可。當選完所有的行或列的時候就能得到總的權值和。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=3e5+10;

ll n,m;
ll a[N],b[N];

int main()
{
   scanf("%lld%lld",&n,&m);
   for(int i=1;i<=n;i++)
       scanf("%lld",&a[i]);
   for(int i=1;i<=m;i++)
       scanf("%lld",&b[i]);
   sort(a+1,a+1+n);sort(b+1,b+1+m);
   ll ans=a[1]*(m-1)+b[1]*(n-1);
   ll cnt1=2,cnt2=2,line=1,row=1;
   while(cnt1<=n&&cnt2<=m)
   {
       if(a[cnt1]<b[cnt2]) ans+=(ll)a[cnt1++]*(m-line),row++;//貪心
       else ans+=(ll)b[cnt2++]*(n-row),line++;
   }
   printf("%lld",ans);
   return 0;
}