题意:nnn个点mmm条边的无向图,每个点有一个正点权,每次选择一个连通子图,将里面的权值都减111。求所有点权为000的最小步数。
T≤10,n≤105,m≤2×105T\leq 10,n\leq 10^5,m\leq2\times10^5T≤10,n≤105,m≤2×105
考虑一个贪心:每次一定选择一个极大的连通块。
感性理解很容易,还是证明一下:
假设一个极大连通块SSS,我偏不选,只选择它的子连通块来覆盖整个SSS,答案严格更优。考虑两个连在一起的连通块T1,T2T_1,T_2T1,T2,选择T1∪T2,T1∩T2T_1\cup T_2,T_1\cap T_2T1∪T2,T1∩T2一定不比选T1,T2T_1,T_2T1,T2劣。因为选择的连通块覆盖了整个SSS,所以可以一步步合并出SSS(即任选一个与当前集合相邻的点,将覆盖它的集合与当前集合合并),答案不会更劣,矛盾。
对于一个连通块来说,一定是点按照权值从小到大被删。把操作顺序倒过来,就是把大的结点减小成和小的结点相同,然后一起删掉。
形式化地讲,就是把权值从大到小排序依次加入,并把全场的权值都减到当前权值。用并查集维护连通块个数即可。
复杂度O(nlogn)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;
}
最小步数清零图节点权值
642

被折叠的 条评论
为什么被折叠?



