题意: n n n个点 m m m条边的无向图,每个点有一个正点权,每次选择一个连通子图,将里面的权值都减 1 1 1。求所有点权为 0 0 0的最小步数。
T ≤ 10 , n ≤ 1 0 5 , m ≤ 2 × 1 0 5 T\leq 10,n\leq 10^5,m\leq2\times10^5 T≤10,n≤105,m≤2×105
考虑一个贪心:每次一定选择一个极大的连通块。
感性理解很容易,还是证明一下:
假设一个极大连通块 S S S,我偏不选,只选择它的子连通块来覆盖整个 S S S,答案严格更优。考虑两个连在一起的连通块 T 1 , T 2 T_1,T_2 T1,T2,选择 T 1 ∪ T 2 , T 1 ∩ T 2 T_1\cup T_2,T_1\cap T_2 T1∪T2,T1∩T2一定不比选 T 1 , T 2 T_1,T_2 T1,T2劣。因为选择的连通块覆盖了整个 S S S,所以可以一步步合并出 S S S(即任选一个与当前集合相邻的点,将覆盖它的集合与当前集合合并),答案不会更劣,矛盾。
对于一个连通块来说,一定是点按照权值从小到大被删。把操作顺序倒过来,就是把大的结点减小成和小的结点相同,然后一起删掉。
形式化地讲,就是把权值从大到小排序依次加入,并把全场的权值都减到当前权值。用并查集维护连通块个数即可。
复杂度 O ( n log n ) O(n\log n) O(nlogn)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
#define MAXN 100005
using namespace std;
typedef long long ll;
vector<int> e[MAXN];
int fa[MAXN];
inline int find(const int& x){return fa[x]==x? x:fa[x]=find(fa[x]);}
int a[MAXN],p[MAXN],vis[MAXN];
inline bool cmp(const int& x,const int& y){return a[x]>a[y];}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
int n,m;
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) e[i].clear(),fa[i]=p[i]=i,vis[i]=0,scanf("%d",&a[i]);
for (int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
e[u].push_back(v),e[v].push_back(u);
}
sort(p+1,p+n+1,cmp);
int cur=1;
ll ans=0;
vis[p[1]]=1;
for (int i=2;i<=n;i++)
{
ans+=(ll)cur*(a[p[i-1]]-a[p[i]]);
++cur;
for (vector<int>::iterator it=e[p[i]].begin();it!=e[p[i]].end();++it)
{
int u=p[i],v=*it;
if (!vis[v]) continue;
u=find(u),v=find(v);
if (u!=v) fa[u]=v,--cur;
}
vis[p[i]]=1;
}
ans+=(ll)cur*a[p[n]];
printf("%lld\n",ans);
}
return 0;
}