[关闭]
@AkamemakA 2022-09-04T01:17:53.000000Z 字数 1854 阅读 107

Atcoder 题解

Atcoder Beginner Contest 267 problem E 题解


题目链接:点我

题目大意

给定一张边的简单无向图,每个点有一个点权。进行次操作,每次操作会删除一个点,删除一个点的代价是:这个点的所邻点的点权和。然后对于后续操作,这个点与连接到这个点的所有边都不复存在。问次操作中,单次代价的最大值最小可以是多少?

样例输入1

  1. 4 3
  2. 3 1 4 2
  3. 1 2
  4. 1 3
  5. 4 1

样例输出1

  1. 3

样例解释1

通过如下次操作,我们可以得到最小的最大值

可以证明,没有一个方案得到的最大值比更小。

样例输入2

  1. 7 13
  2. 464 661 847 514 74 200 188
  3. 5 1
  4. 7 1
  5. 5 7
  6. 4 1
  7. 4 5
  8. 2 4
  9. 5 2
  10. 1 3
  11. 1 6
  12. 3 5
  13. 1 2
  14. 4 6
  15. 2 7

样例输出2

  1. 1199

数据范围


解析

求最大值最小,二分??

其实,这道题是一道贪心。我们仔细想想,我们要找到最小的最大值,那么我们在每次删点操作时,肯定是更贪心的选择所有未删的点中花费代价最少的点。

所以,每次操作只要找到所有点中花费代价最少的点即可。

但是怎么找呢?双层枚举, ?肯定会炸。因此,我们必须来找一些优化的技巧。

对于快速找到最小值,且可以插入删除指定元素的容器,你们想到了什么?没错,那就是

在读入时,我们就可以处理出删除一个点所需要花费的代价。

我们把所有未删的点都插入容器中,那么每次删点操作直接取第一个元素即可。因为内部已经按照从小到大排好了序。

那么删点之后如何更新呢?

我们可以枚举这个点的所有邻点,然后这些邻点的原有代价减去这个点的点权即可。因为这个点被删除,那么它的邻点被删除时,所需要计算的点会少了当前这一个点。(如果不好理解,可以看代码并画图模拟一下。)

总时间复杂度


代码实现

  1. #include<iostream>
  2. #include<algorithm>
  3. #include<set>
  4. using namespace std;
  5. #define int long long
  6. const int N=4e5+5;
  7. typedef pair<int,int> pii;
  8. int n,m,val[N],ans;
  9. int he[N],ne[N],go[N],tot;
  10. set<pii> s;//存储未删除的点。因为我们需要知道当前删除的点的编号,因此采用pair将代价与编号捆绑起来。
  11. int deg[N]; //deg[i]表示删除i号点所需要的代价。
  12. bool vis[N]; //这里有一个小细节:我们在删一个点,枚举其邻点时,必须要判断这个点是否已经被删除。否则会WA得不知所措(悲
  13. inline void add(int a,int b){
  14. ne[++tot]=he[a];he[a]=tot;go[tot]=b;
  15. }//前向星建图
  16. signed main()
  17. {
  18. cin>>n>>m;
  19. for(int i=1;i<=n;i++)
  20. cin>>val[i];
  21. for(int i=1;i<=m;i++){
  22. int u,v;cin>>u>>v;
  23. add(u,v);add(v,u);
  24. deg[u]+=val[v];deg[v]+=val[u]; //处理deg数组
  25. }
  26. for(int i=1;i<=n;i++) s.insert({deg[i],i}); //开始所有的点都未被删除
  27. for(int i=1;i<=n;i++){
  28. auto t=s.begin();s.erase(t); //取set首元素并删除
  29. pii res=*t;//迭代器转pii
  30. vis[res.second]=1;//标记该点已被删除
  31. ans=max(ans,deg[res.second]);//用删去这个点所需要的代价更新答案
  32. for(int j=he[res.second];j;j=ne[j]){
  33. int v=go[j]; //枚举邻点
  34. if(vis[v]) continue; //如果这个点已经被删除,则跳过
  35. s.erase({deg[v],v}); //取出编号为v的这个点
  36. deg[v]-=val[res.second]; //更新删去这个点所需要的的代价
  37. s.insert({deg[v],v}); //放回这个点
  38. }
  39. //set会自动排序,以便下一次删点。
  40. }
  41. cout<<ans;
  42. }

一定要判断是否被删去!!!血的教训

完结撒花~~~

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注