【2020杭电多校】Total Eclipse 并查集+思维
题目链接:Total Eclipse
题意:
t组输入,给你一个由n个点,m条边构成的图,每一个点的权值是ai。你每一次可以选择一批联通的点,然后让他们的权值都减去1。问最后把所有点的权值都变成0需要多少次操作
题解:
最简单的思路就是每一次dfs找到图中联通的最长的链,然后让这条链减去链上所有点中那个最小的权值。之后这条链就断了。然后再去找链,,依次。。但是这样复杂度太高了,就会TLE
如果每一次找到的链上所有点的权值是有序的,那么我们就可以一次得到将这条链上的点的权值变成0的最小操作数,比如下面一条权值链
9->8->7->6->2->1,那么把这些点权值变成0只需要9次
那么我们可以从新构建一下这个图,我们可以按照这些点的权值从大到小排序,然后遍历它,先找到一个点,那么这个点就独自构成一个连通块,如果下一个点和之前点相连,那么这两个点就在一个连通块上,连通块数量不加1,否则连通块数量加1(如果这个点将多个连通块相连,那么连通块个数就不止减1了)。我们先说连通块数量减1情况,我们的答案要加上(a[2]-a[3])*ans (ans是连通块数量,a数组就是题目上的权值数组),他代表的意思就是a[2]点相对于a[3]点要多做几次操作,乘于ans是,有ans个连通块都需要多做a[2]-a[3]次操作。(在加入第一个点的时候也要进行这样的操作)
模拟过程可以看一下这篇博客:https://blog.csdn.net/yangzijiangac/article/details/107559199
代码:
#include <cstdio> #include <algorithm> #include <iostream> #include <vector> #include <map> #include <queue> #include <set> #include <ctime> #include <cstring> #include <cstdlib> #include <math.h> using namespace std; typedef long long ll; const int maxn=1e5+10; vector<ll>w[maxn]; ll n,m,v[maxn],vis[maxn]; struct shudui { ll pos,val; }a[maxn]; bool mmp(shudui x,shudui y) { return x.val>y.val; } ll finds(ll x) { if(x!=v[x]) { ll y=finds(v[x]); v[x]=y; return y; } return x; } int main() { ll t; scanf("%lld",&t); while(t--) { memset(vis,0,sizeof(vis)); scanf("%lld%lld",&n,&m); for(ll i=1;i<=n;++i) w[i].clear(); for(ll i=1;i<=n;++i) { v[i]=i; scanf("%lld",&a[i].val); a[i].pos=i; } sort(a+1,a+1+n,mmp); a[n+1].val=0; while(m--) { ll x,y; scanf("%lld%lld",&x,&y); w[x].push_back(y); w[y].push_back(x); } ll sum=0,ans=0; for(ll i=1;i<=n;++i) { ans++; ll x=a[i].pos;vis[x]=1; for(ll j=0;j<w[x].size();++j) { ll pos=w[x][j]; if(vis[pos]) { ll fx=finds(x); ll fy=finds(pos); if(fx!=fy) { v[fx]=fy;ans--; } } } sum=sum+ans*(a[i].val-a[i+1].val); } printf("%lld\n",sum); } return 0; }