原文链接 https://www.cnblogs.com/cly-none/p/CSA72G.html

题意:有一个\(n \times n\)的矩阵\(A\),\(m\)次操作,每次在\(A\)上三角部分的一个子矩形中加上一个数。最后构造\(n\)个点的图\(G\),且对于所有\(i,j \ (i < j)\),边\((i,j)\)的边权为\(A_{i,j}\)。求图\(G\)的最小生成树的边权和。

\(n,m \leq 10^5\)

先把上三角矩阵补成邻接矩阵。这样每次操作就是加两个邻接矩阵的子矩形。

这种题目通常要对经典算法进行拓展。常用的最小生成树算法有Prim和Kruskal,然而在尝试之后我们发现,由于边权种类太多,Prim不行;同样Kruskal也难以提出排序后的边权。

但还有Borůvka。这个算法要求对每个联通块找到边权最小的邻边,还要合并联通块。后者用并查集能容易实现,现在仅考虑前者。

先想一个更简单的问题:对每个结点找到边权最小的邻边。这是简单的,我们只需要扫描邻接矩阵的每一行,这样每次矩形加都变成了两个区间加。用线段树维护最小值就好了。考虑原问题。这个最小值可能和这个点在同一个联通块内,因此,非常套路地,我们就再记录与最小值不在一个联通块内的次小值就可以了。

因为上述扫描线需要执行\(O(\log n)\)次,故复杂度为\(O(n \log^2 n)\)。

  1. #include <bits/stdc++.h>
  2. #define data DATA
  3. using namespace std;
  4. #define gc() getchar()
  5. template <typename tp>
  6. inline void read(tp& x) {
  7. x = 0; char tmp; bool key = 0;
  8. for (tmp = gc() ; !isdigit(tmp) ; tmp = gc())
  9. key = (tmp == '-');
  10. for ( ; isdigit(tmp) ; tmp = gc())
  11. x = (x << 3) + (x << 1) + (tmp ^ '0');
  12. if (key) x = -x;
  13. }
  14. typedef long long ll;
  15. const int N = 100010;
  16. const ll INF = 1ll << 60;
  17. int n,m,uni[N],cnt;
  18. ll ans;
  19. int getfa(int pos) {
  20. return pos == uni[pos] ? pos : uni[pos] = getfa(uni[pos]);
  21. }
  22. struct data {
  23. int p,l,r,v;
  24. bool operator < (const data& a) const {
  25. return p < a.p;
  26. }
  27. } dat[N << 2];
  28. typedef pair<ll,int> pli;
  29. #define fi first
  30. #define se second
  31. struct node {
  32. pli mn[2];
  33. ll tag;
  34. node() {
  35. tag = 0;
  36. mn[0] = mn[1] = pli(INF,0);
  37. }
  38. } t[N << 2];
  39. void puttag(int x,ll v) {
  40. t[x].mn[0].fi += v;
  41. t[x].mn[1].fi += v;
  42. t[x].tag += v;
  43. }
  44. void push_down(int x) {
  45. puttag(x<<1,t[x].tag);
  46. puttag(x<<1|1,t[x].tag);
  47. t[x].tag = 0;
  48. }
  49. void push_up(node& x,node ls,node rs) {
  50. x.mn[1].fi = INF;
  51. if (ls.mn[0] < rs.mn[0]) {
  52. x.mn[0] = ls.mn[0];
  53. if (rs.mn[0].se != x.mn[0].se)
  54. x.mn[1] = rs.mn[0];
  55. } else {
  56. x.mn[0] = rs.mn[0];
  57. if (ls.mn[0].se != x.mn[0].se)
  58. x.mn[1] = ls.mn[0];
  59. }
  60. if (ls.mn[1].se != x.mn[0].se)
  61. x.mn[1] = min(x.mn[1], ls.mn[1]);
  62. if (rs.mn[1].se != x.mn[0].se)
  63. x.mn[1] = min(x.mn[1], rs.mn[1]);
  64. }
  65. void modify(int l,int r,ll v,int x=1,int lp=1,int rp=n) {
  66. if (lp > r || l > rp) return;
  67. if (lp >= l && rp <= r)
  68. return (void) puttag(x,v);
  69. push_down(x);
  70. int mid = (lp + rp) >> 1;
  71. modify(l,r,v,x<<1,lp,mid);
  72. modify(l,r,v,x<<1|1,mid+1,rp);
  73. push_up(t[x],t[x<<1],t[x<<1|1]);
  74. }
  75. void build(int x=1,int lp=1,int rp=n) {
  76. t[x].tag = 0;
  77. if (lp == rp) {
  78. t[x].mn[0] = pli(0ll,uni[lp]);
  79. t[x].mn[1] = pli(INF,0);
  80. return;
  81. }
  82. int mid = (lp + rp) >> 1;
  83. build(x<<1,lp,mid);
  84. build(x<<1|1,mid+1,rp);
  85. push_up(t[x], t[x<<1], t[x<<1|1]);
  86. }
  87. pli nex[N];
  88. void solve() {
  89. build();
  90. for (int i = 1 ; i <= n ; ++ i)
  91. nex[i] = pli(INF,0);
  92. for (int i = 1, j = 1 ; i <= n ; ++ i) {
  93. while (j <= cnt && dat[j].p <= i)
  94. modify(dat[j].l, dat[j].r, dat[j].v), ++ j;
  95. node tmp = t[1];
  96. if (tmp.mn[0].se != uni[i])
  97. nex[uni[i]] = min(nex[uni[i]], tmp.mn[0]);
  98. else nex[uni[i]] = min(nex[uni[i]], tmp.mn[1]);
  99. }
  100. for (int i = 1, j ; i <= n ; ++ i) {
  101. j = getfa(i);
  102. if (nex[j].se == INF) continue;
  103. if (getfa(nex[j].se) != j) {
  104. ans += nex[j].fi;
  105. uni[j] = getfa(nex[j].se);
  106. }
  107. }
  108. }
  109. bool check() {
  110. for (int i = 1 ; i <= n ; ++ i)
  111. uni[i] = getfa(i);
  112. for (int i = 2 ; i <= n ; ++ i)
  113. if (uni[i] != uni[i-1]) return 1;
  114. return 0;
  115. }
  116. signed main() {
  117. read(n), read(m);
  118. for (int i = 1, a, b, c, d, e ; i <= m ; ++ i) {
  119. read(a), read(b), read(c), read(d), read(e);
  120. dat[++cnt] = (data) {a, c, d, e};
  121. dat[++cnt] = (data) {b+1, c, d, -e};
  122. dat[++cnt] = (data) {c, a, b, e};
  123. dat[++cnt] = (data) {d+1, a, b, -e};
  124. }
  125. for (int i = 1 ; i <= n ; ++ i)
  126. uni[i] = i;
  127. sort(dat+1,dat+cnt+1);
  128. while (check())
  129. solve();
  130. cout << ans << endl;
  131. return 0;
  132. }

小结:本题做法乍一看是几个套路的综合,但并不简单。还是要求清晰的思维,以及熟练掌握基础算法和技巧。

【做题】CSA72G - MST and Rectangles——Borůvka&线段树的更多相关文章

  1. 【CSA72G】【XSY3316】rectangle 线段树 最小生成树

    题目大意 有一个 \(n\times n\) 的矩阵 \(A\).最开始 \(A\) 中每个元素的值都为 \(0\). 有 \(m\) 次操作,每次给你 \(x_1,x_2,y_1,y_2,w\),对 ...

  2. 刷题总结——二逼平衡树(bzoj3224线段树套splay)

    题目: Description 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:1.查询k在区间内的排名2.查询区间内排名为k的值3.修改某一位值上的数值4.查询k在 ...

  3. GSS4 - Can you answer these queries IV || luogu4145上帝造题的七分钟2 / 花神游历各国 (线段树)

    GSS4 - Can you answer these queries IV || luogu4145上帝造题的七分钟2 / 花神游历各国 GSS4 - Can you answer these qu ...

  4. Codeforces 1396D - Rainbow Rectangles(扫描线+线段树)

    Codeforces 题面传送门 & 洛谷题面传送门 一道鸽了整整一年的题目,上一次提交好像是 2020 年 9 月 13 日来着的(?) 乍一看以为第 2 个提交和第 3 个提交只差了 43 ...

  5. Gym - 101982F Rectangles (扫描线+线段树)

    链接:http://codeforces.com/gym/101982/attachments 思路: 问被覆盖次数为奇数次的矩阵的面积并 扫描线求矩阵面积并我们是上界赋为-1,下界赋为1,因为要求覆 ...

  6. day1 晚上 P4145 上帝造题的七分钟2 / 花神游历各国 线段树

    #include<iostream> #include<cstdio> #include<cmath> using namespace std; ; struct ...

  7. [日记&做题记录]-Noip2016提高组复赛 倒数十天

    写这篇博客的时候有点激动 为了让自己不颓 还是写写日记 存存模板 Nov.8 2016 今天早上买了两个蛋挞 吃了一个 然后就做数论(前天晚上还是想放弃数论 但是昨天被数论虐了 woc noip模拟赛 ...

  8. NOIP2016考前做题(口胡)记录

    NOIP以前可能会持续更新 写在前面 NOIP好像马上就要到了,感觉在校内训练里面经常被虐有一种要滚粗的感觉(雾.不管是普及组还是提高组,我都参加了好几年了,结果一个省一都没有,今年如果还没有的话感觉 ...

  9. zoj-1610线段树刷题

    title: zoj-1610线段树刷题 date: 2018-10-16 16:49:47 tags: acm 刷题 categories: ACM-线段树 概述 这道题是一道简单的线段树区间染色问 ...

随机推荐

  1. linux基础命令--groupadd 创建新的群组

    描述 groupadd命令用于创建一个新的群组. groupadd命令默认会根据命令行指定的值和系统下的/etc/login.defs文件定义的值去修改系统下的/etc/group和/etc/gsha ...

  2. VUE-008-通过路由 router.push 传递 query 参数(路由 path 识别,请求链接显示参数传递)

    在前端页面表单列表修改时,经常需要在页面切换的时候,传递需要修改的表单内容,通常可通过路由进行表单参数的传递. 首先,配置页面跳转路由.在 router/index.js 中配置相应的页面跳转路由,如 ...

  3. innerHTML .innerText区别

    ().innerHtml("“):改变html元素: ().innerTEXT(”“):改变文本元素: 试验代码 <!DOCTYPE html> <html lang=&q ...

  4. pycharm 激活方法

    方法一: 服务器激活 pycharm 安装: https://www.cnblogs.com/pyyu/articles/9210171.html 方法二: 密钥激活 pycharm 获取激活码 (密 ...

  5. Applet学习教程(一):applet+dwr 实现

    后台代码 import java.applet.Applet; import java.util.HashMap; import java.util.Map; import netscape.java ...

  6. 代码块: 以冒号作为开始,用缩进来划分作用域,这个整体叫做代码块,python的代码块可以提升整体的整齐度,提高开发效率

    # ### 代码块: 以冒号作为开始,用缩进来划分作用域,这个整体叫做代码块 if 5 == 5: print(1) print(2) if True: print(3) print(4) if Fa ...

  7. 解决git冲突造成的Please move or remove them before you can merge

    git clean -d -fx “” 其中x —–删除忽略文件已经对git来说不识别的文件d —–删除未被添加到git的路径中的文件f —–强制运行如果你确定这货已经没用了,并且git status ...

  8. Unable to find a constructor to use for type System.Security.Claims.Claim. A class should either have a default constructor

    Newtonsoft.Json DeserializeObject 反序列化  IdentityServer4.Models Cliecnt 错误: Newtonsoft.Json.JsonSeria ...

  9. centos7.2 Apache+PHP7.2+Mysql5.6环境搭建

    yum安装PHP7.2 由于linux的yum源不存在php7.x,所以我们要更改yum源:rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-re ...

  10. JDK1.8 HashMap--treeifyBin()方法

    /*树形化*/ final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e;// ...