【HNOI2010】城市建设(对时间分治 & Kruskal)
Description
\(n\) 个点 \(m\) 条边的带边权无向图。\(q\) 次操作,每次修改一条边的权值。
求每次修改后的最小生成树的边权和。
Hint
\(1\le n\le 2\times 10^4, 1\le m, q\le 5\times 10^4, 1\le \text{边权}\le 5\times 10^7\)
Solution
考虑对时间进行分治,\(\textbf{solve}(l, r)\) 表示处理第 \(l\) 到第 \(r\) 个操作,并对原图生效这些修改的过程。
显然如果在 \(l = r\) 时直接将 \(m\) 条边放一起跑 MST 那好像是为了分治而分治。分治是将问题划分达到 减小规模 的目的,然而这里在 \(\textbf{solve}\) 的每一层中我们的问题规模并没有变。
求 MST 的时间消耗主要取决于在 边的规模。思考如何可以把边的规模缩小。
首先我们将当前 \(\textbf{solve}(l, r)\) 过程的边进行分类:动态边(存在一个在 \([l, r]\) 中的操作对该边进行了修改)以及 静态边(没有位于 \([l, r]\) 的对其修改的操作)。
动态边,对于当下来说,是不能随便剪掉的,于是着重对静态边考虑。其中有一部分边是当前及之后的分治中 必定会选中的边,还有是 必定不会选的边。对于必定会选中的边,我们完全可以把这些边跑 Kruskal 先在 \(\textbf{solve}(l, r)\) 中做掉,这和在分治终点处做 \(r-l+1\) 遍的效果是一样的;而对于必定不会选的边,在 \(\textbf{solve}(l, r)\) 中直接扔掉也无妨,因为这些边迟早会在分治终点处再扔 \(r-l+1\) 遍。
现在要做的就是设计这两个过程,这里分别记为 \(\textbf{contraction}\) 和 \(\textbf{reduction}\)。
\(\textbf{contraction}\)
我们先将所有当前的动态边都在并查集中连上,然后再一个个连静态边,并记录下来所有成功连上的静态边。这些成功连上的就是必选边。这里一开始就连动态边,旨在把这些边按修改地尽量小考虑(此处相当于令动态边权 \(=-\infty\)),也就是说不管这些边会被改的多小,那些成功连上的照样选上。
\(\textbf{reduction}\)
我们先把所有当前的动态边都略过,只连静态边,并记录未成功连上的边。这些未成功连上的就是无用边。这里将动态边直接忽略,旨在把这些边按修改地尽量大考虑(此处相当于令动态边权 \(=+\infty\)),即无论这些动态边被改大到什么地步,给静态边留了多少的余地,这部分为成功连上的对结果仍然没有贡献。
通过这两个过程,边的规模得到了减小。那么具体减到多小呢?
\(\textbf{contraction}\) 中,我们有不超过 \(r-l+1\) 条动态边,于是就有不少于 \(n-(r-l+1)-1\) 条必选边。这些边会使原图中的连通块消至 \(O(r-l+1)\) 的规模;\(\textbf{reduction}\) 中,由于 \(\textbf{contraction}\) 后需要考虑的点数已经缩小至区间长度级别,那么有用的边只能有点数规模这么多。
于是总复杂度 \(T(n) = 2T(\frac n 2) + O(n\log n) = O(n\log^2 n)\)。
最后在分治的终点,记得先修改,然后把当前边集中剩下的一点也给做掉记录答案。
具体实现时其实还有许多细节:
- 并查集需要支持撤销,于是不能用路径压缩,只能启发式、按秩合并。
- 如果
solve
函数中有用vector
作为参数表示当前边集的话,千万不要加&
引用。左边递归后再到右侧会把边集改掉然后出大问题。 - 不开
long long
见祖宗。
Code
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : HNOI2010 城市建设
*/
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
const int N = 2e4 + 5;
const int M = 5e4 + 5;
int n, m, Q;
struct unionFind{
int fa[N], siz[N];
int stk[N], top;
void reset(int n) {
for (int i = 1; i <= n; i++)
fa[i] = i, siz[i] = 1;
top = 0;
}
int find(int x) {
return x == fa[x] ? x : find(fa[x]);
}
void merge(int x, int y) {
if ((x = find(x)) == (y = find(y))) return;
if (siz[x] > siz[y]) swap(x, y);
fa[x] = y, siz[y] += siz[x], stk[++top] = x;
}
void back(int t) {
while (top > t) {
int x = stk[top--];
siz[fa[x]] -= siz[x], fa[x] = x;
}
}
} ufs;
struct Edge {
int u, v, w;
inline bool operator < (const Edge& rhs) const {
return w < rhs.w;
}
} e[M];
struct Query {
int k, d;
} q[M];
long long ans[M];
bool vis[M];
bool cmp(const int& x, const int& y) {
return e[x] < e[y];
}
void contraction(int l, int r, vector<int>& can_e, long long& val) {
int backtr = ufs.top; vector<int> rec;
sort(can_e.begin(), can_e.end(), cmp);
for (int i = l; i <= r; i++) ufs.merge(e[q[i].k].u, e[q[i].k].v);
for (auto i : can_e) if (!vis[i]) {
int u = ufs.find(e[i].u), v = ufs.find(e[i].v);
if (u != v) rec.emplace_back(i), ufs.merge(u, v), val += e[i].w;
}
ufs.back(backtr);
for (auto i : rec) ufs.merge(e[i].u, e[i].v);
}
void reduction(vector<int>& can_e) {
int backtr = ufs.top; vector<int> rec;
sort(can_e.begin(), can_e.end(), cmp);
for (auto i : can_e)
if (vis[i]) {
rec.emplace_back(i);
} else {
int u = ufs.find(e[i].u), v = ufs.find(e[i].v);
if (u != v) ufs.merge(u, v), rec.emplace_back(i);
}
ufs.back(backtr), rec.swap(can_e);
}
void solve(int l, int r, vector<int> can_e, long long val) {
if (l == r) e[q[l].k].w = q[l].d;
int backtr = ufs.top;
if (l == r) {
sort(can_e.begin(), can_e.end(), cmp);
for (auto i : can_e) {
int u = ufs.find(e[i].u), v = ufs.find(e[i].v);
if (u != v) ufs.merge(u, v), val += e[i].w;
}
ans[l] = val;
return ufs.back(backtr);
}
for (int i = l; i <= r; i++) vis[q[i].k] = 1;
contraction(l, r, can_e, val), reduction(can_e);
for (int i = l; i <= r; i++) vis[q[i].k] = 0;
int mid = (l + r) >> 1;
solve(l, mid, can_e, val), solve(mid + 1, r, can_e, val);
return ufs.back(backtr);
}
signed main() {
ios::sync_with_stdio(false);
cin >> n >> m >> Q, ufs.reset(n); vector<int> tmp;
for (int i = 1; i <= m; i++) cin >> e[i].u >> e[i].v >> e[i].w;
for (int i = 1; i <= m; i++) tmp.emplace_back(i);
for (int i = 1; i <= Q; i++) cin >> q[i].k >> q[i].d;
solve(1, Q, tmp, 0ll);
for (int i = 1; i <= Q; i++) cout << ans[i] << endl;
return 0;
}
【HNOI2010】城市建设(对时间分治 & Kruskal)的更多相关文章
- 【BZOJ2001】[HNOI2010]城市建设(CDQ分治,线段树分治)
[BZOJ2001][HNOI2010]城市建设(CDQ分治,线段树分治) 题面 BZOJ 洛谷 题解 好神仙啊这题.原来想做一直不会做(然而YCB神仙早就切了),今天来怒写一发. 很明显这个玩意换种 ...
- BZOJ2001 [Hnoi2010]City 城市建设 【CDQ分治 + kruskal】
题目链接 BZOJ2001 题解 CDQ分治神题... 难想难写.. 比较朴素的思想是对于每个询问都求一遍\(BST\),这样做显然会爆 考虑一下时间都浪费在了什么地方 我们每次求\(BST\)实际上 ...
- P3206 [HNOI2010]城市建设 [线段树分治+LCT维护动态MST]
Problem 这题呢 就边权会在某一时刻变掉-众所周知LCT不支持删边的qwq- 所以考虑线段树分治- 直接码一发 如果 R+1 这个时间修改 那就当做 [L,R] 插入了一条边- 然后删的边和加的 ...
- [HNOI2010]城市建设
[HNOI2010]城市建设 玄学cdq O(nlog^2n)的动态最小生成树 其实就是按照时间cdq分治+剪枝(剪掉一定出现和不可能出现的边) 处理[l,r]之间的修改以及修改之后的询问,不能确定是 ...
- 【LG3206】[HNOI2010]城市建设
[LG3206][HNOI2010]城市建设 题面 洛谷 题解 有一种又好想.码得又舒服的做法叫线段树分治+\(LCT\) 但是因为常数过大,无法跑过此题. 所以这里主要介绍另外一种玄学\(cdq\) ...
- 【CDQ分治】[HNOI2010]城市建设
题目链接 线段树分治+LCT只有80 然后就有了CDQ分治的做法 把不可能在生成树里的扔到后面 把一定在生成树里的扔到并查集里存起来 分治到l=r,修改边权,跑个kruskal就行了 由于要支持撤销, ...
- BZOJ2001 HNOI2010城市建设(线段树分治+LCT)
一个很显然的思路是把边按时间段拆开线段树分治一下,用lct维护MST.理论上复杂度是O((M+Q)logNlogQ),实际常数爆炸T成狗.正解写不动了. #include<iostream> ...
- 洛谷P3206 [HNOI2010]城市建设
神仙题 题目大意: 有一张\(n\)个点\(m\)条边的无向联通图,每次修改一条边的边权,问每次修改之后这张图的最小生成树权值和 话说是不是\(cdq\)题目都可以用什么数据结构莽过去啊-- 这道题目 ...
- BZOJ2001 HNOI2010 城市建设
题目大意:动态最小生成树,可以离线,每次修改后回答,点数20000,边和修改都是50000. 顾昱洲是真的神:顾昱洲_浅谈一类分治算法 链接: https://pan.baidu.com/s/1c2l ...
随机推荐
- vue 使用中的小技巧 (一)
在vue的使用过程中会遇到各种场景,当普通使用时觉得没什么,但是或许优化一下可以更高效更优美的进行开发.下面有一些我在日常开发的时候用到的小技巧 data 和 Object.freeze 每个Vue实 ...
- 每日理解(一) Spring框架
每日理解 SpringIOC 控制反转 在Java SE中通过new来创建对象.而在Spring中通过容器来控制对象. 所谓的控制包括:对象的创建.初始化.以及销毁.我们有之前的主动控制对象,变为了S ...
- Android 滑动删除控件推荐
implementation 'com.github.mcxtzhang:SwipeDelMenuLayout:V1.3.0' <?xml version="1.0" enc ...
- 应用程序-特定 权限设置并未向在应用程序容器不可用 SID (不可用)中运行的地址 LocalHost (使用 LRPC) 中的用户...的 COM 服务器应用程序的 本地 激活 权限。此安全权限可以使用组件服务管理工具进行修改。
很久以前发现我们的业务服务器上出现一个System的系统严重错误,查找很久都没有找到解决办法,今日再次查看服务器发现报错更频繁,于是就搜集各种资料进行查找解决办法,终于找到了一个解决办法. 错误截图介 ...
- 探究:nuget工具对不再使用的dll文件的处理策略
背景介绍 nuget是.net平台有效的包管理工具,相信每个C#开发者对它都不陌生. 本文我们来探究一下nuget对不再使用的dll文件的处理策略,分为如下2个场景: 场景A:包A1.0原来包含New ...
- RSA(攻防世界)Rsa256 -- cr4-poor-rsa
RSA256 [攻防世界] 题目链接 [RSA256] 下载附件得到两个文件. 猜测第一个 txt 文件 可能为RSA加密密文 ,第二个估计就是密钥.依次打开看看: 果然如此. 目标: 寻找 n.e. ...
- 如何在Camtasia中对录制视频添加注释
今天我给大家带来的是一款专门录制屏幕动作的软件Camtasia,拥有了使我们的屏幕录像拥有全新的剪辑速度和更换颜色背景的特性.它不仅可以完成我们屏幕录像的心愿,还可以进行对录制的视频进行后期的编辑.这 ...
- Jmeter如何监测被测服务器资源
前言 Jmeter自身不支持对服务器的监控,需要安装第三方插件进行扩展. 下载插件 jmeter添加插件步骤,选项-PluginManager 勾选上PerfMon选项,点击右下角的Apply-按钮 ...
- Session 与 sql 会话,mysql 权限设置,mybatis 逆向工程
Session 与 Sql 会话注意点: 通过 sqlSessionFactoty 工厂建立的与sql的会话,在进行相应的插入操作后,需要进行 commit 操作才会让数据库执行插入更新操作.如何主键 ...
- G - Pyramid 题解(打表)
题目链接 题目大意 t组数据,给你一个n(n<=1e9)求高度为n的等边三角形,求里面包含了多少个等边三角形 题目思路 打表找规律,然而我一直没找到规律. 看到题解恍然大悟,答案就是C(n+3, ...