当前位置: 首页 > news >正文

P14164 [ICPC 2022 Nanjing R] 命题作文

给定一个包含 \(n\) 个点的链,\(m\) 次每次额外添加一条边,操作之间不独立。每次操作完询问有多少种方案选出两条边使得删除这两条边之后图不联通。

\[n,m \le 2.5\times10^5 \]


称额外添加的边为额外边,原来的 \(n-1\) 条边为原始边。

考虑什么样的边可以成为答案:

  • 其中一条是割边。

  • 其中一条原始边被且仅被另外一条额外边覆盖。

  • 两条不是割边的原始边,且覆盖他们的额外边构成的集合完全相同。

前两种情况是显然的。以下是第三种情况的证明:

证明

假设覆盖他们的额外边构成的集合完全相同,那么对于这两条边把原链切成的三段,所有的额外边要么连接这三段内部的点,要么连接最左边那一段和最右边那一段的点。那么中间那一段肯定和其他两段是分开的。

假设覆盖他们的额外边构成的集合不同,那么由于他们都不是割边,肯定存在一条边同时覆盖他们两个。又由于存在一条边覆盖其中一个但是不覆盖另外一个,那么肯定这三段都是联通的。

前两个本质是要维护被覆盖了 0/1 次的边的个数,这个是可以线段树简单维护的。

考虑如何维护第三种情况。正着做是比较困难的。我们考虑倒着做。

我们先找出最终状态下有哪些组,每条边在那个组里。然后找出每个时刻哪些组会被合并。

我们对每条边维护一个 01 串。表示这条边是否被第 \(i\) 条额外边覆盖。

那么我们对这些串按字典序排序,初始相邻的相同串在同一个组里,相邻的两个串的 LCP 就是他们被合并的时间。

那么这个过程用可持久化线段树维护哈希,是简单的。

\(\mathcal O(n\log^2 n)\)


#include <algorithm>
#include <iostream>
#include <vector>
#include <tuple>
#include <random>
#include <set>const int N = 2.5e5 + 7;
#define rep(i,a,b) for(int i(a);i<=(b);++i)
typedef long long i64;struct Tree1 {struct node {int l, r, cnt0, cnt1, tag;} tr[4*N];
#define ls (rt<<1)
#define rs (rt<<1|1)
#define self tr[rt]inline void addtag(int rt, int tag) {self.tag += tag;if(tag == 1) {self.cnt1 = self.cnt0, self.cnt0 = 0;} else {self.cnt0 = self.cnt1 = 0;}}inline void pushdown(int rt) {if(self.tag) {addtag(ls, self.tag);addtag(rs, self.tag);self.tag = 0;}}inline void pushup(int rt) {self.cnt0 = tr[ls].cnt0 + tr[rs].cnt0;self.cnt1 = tr[ls].cnt1 + tr[rs].cnt1;}void build(int l, int r, int rt=1) {self.l = l, self.r = r; self.cnt0 = r - l + 1;self.cnt1 = self.tag = 0;if(l < r) {int m = (l + r) >> 1;build(l, m, ls), build(m+1, r, rs), pushup(rt);}}void modify(int x, int y, int rt=1) {if(x > self.r || self.l > y) return ;if(x <= self.l and self.r <= y) return addtag(rt, 1);return pushdown(rt), modify(x, y, ls), modify(x, y, rs), pushup(rt);}
} T;std::mt19937_64 rnd(std::random_device{}());
typedef unsigned long long u64;
u64 hash[N];
struct hash_initualizer {hash_initualizer() {for(int i = 0; i < N; ++i) hash[i] = std::abs((i64)rnd());}
} _hash_initualizer;int n, m;#undef ls
#undef rs
struct Tree2 {struct node {int ls, rs;u64 hash;} tr[N*64];int idx = 1;
#define self tr[rt]
#define prev tr[pr]void update(int& rt, int pr, int x, int l=1, int r=m+1) {rt = ++idx;self = prev;self.hash ^= hash[x];if(l == r) { return ; }	int m = (l + r) >> 1;if(x <= m) update(self.ls, prev.ls, x, l, m);else update(self.rs, prev.rs, x, m+1, r);}std::pair<int, i64> query(int rt, int pr, int l=1, int r=m+1) {if(l == r) { return {l - 1, self.hash - prev.hash}; }int m = (l + r) >> 1;if(tr[self.ls].hash == tr[prev.ls].hash) return query(self.rs, prev.rs, m+1, r);else return query(self.ls, prev.ls, l, m);}
} S;int c0[N], c1[N];struct Query { int x, y; } q[N];
int rt[N];int fa[N], siz[N];
inline int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }auto C = [](int x) { return 1ll * x * (x - 1) / 2; };i64 sum, ans[N];
inline void merge(int x, int y) {x = find(x), y = find(y);if(x != y) {sum -= C(siz[x]), sum -= C(siz[y]);siz[y] += siz[x];fa[x] = y;sum += C(siz[y]);}
}void clear() { S.idx = 1; }std::basic_string<int> mod[N];
std::vector<std::pair<int, int>> con[N];
void solve() {std::cin >> n >> m;clear();T.build(1, n-1);rep(i, 1, n) mod[i].clear();rep(i, 1, m) con[i].clear();for(int i = 1; i <= m; ++i) {int l, r; std::cin >> l >> r;if(l > r) std::swap(l, r);if(l == r) {c0[i] = T.tr[1].cnt0;c1[i] = T.tr[1].cnt1;continue;}--r;mod[l] += i;mod[r+1] += i;T.modify(l, r);c0[i] = T.tr[1].cnt0;c1[i] = T.tr[1].cnt1;}for(int i = 1; i < n; ++i) {rt[i] = rt[i-1];for(int y: mod[i]) {S.update(rt[i], rt[i], y);}	}static int w[N];rep(i, 1, n) w[i] = i;std::set<std::pair<int, int>> set;std::stable_sort(w + 1, w + n, [&](int x, int y) -> bool {return S.query(rt[x], rt[y]).second < 0;});for(int i = 1; i <= n - 2; ++i) {int x = w[i], y = w[i+1];auto [p, diff] = S.query(rt[x], rt[y]);con[p].emplace_back(x, y);}sum = 0;rep(i, 1, n) fa[i] = i, siz[i] = 1;for(int i = m; i >= 1; --i) {for(auto&& [x, y]: con[i]) { merge(x, y); }i64 res = 0;res += 1ll * c0[i] * (n - 2 + i) - C(c0[i]);res += c1[i];res += sum - C(c0[i]);ans[i] = res;}for(int i = 1; i <= m; ++i) {std::cout << ans[i] << "\n";}
}int main() {std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);int t; std::cin >> t; for(; t--; ) solve();
}
http://www.hskmm.com/?act=detail&tid=31763

相关文章:

  • C语言学习——整数变量
  • 语音合成技术从1秒样本学习表达风格
  • 我的高敏感和家人
  • 对称多项式
  • usb储存之BOT/UAS内核驱动
  • 简述flux思想?
  • 风控评分卡
  • 20232428 2025-2026-1 《网络与系统攻防技术》实验一实验报告
  • JAVA对象内存布局
  • 20232409 2025-2026-1 《网络与系统攻防技术》实验二实验报告
  • 10月15号
  • 记录一次客户现场环境,银河麒麟V10操作系统重启后,进入登录页面后卡死,鼠标键盘无响应的解决过程
  • 图 生成树
  • DolphinScheduler 3.1.9 单机版重启后,项目、流程定义等数据全部丢失
  • ManySpeech.AliParaformerAsr 使用指南
  • 资料拿取表
  • 易路:以“薪酬科技+AI”重塑中国企业薪酬管理新范式
  • 2025年太阳能板终极指南:选择、趋势与品牌推荐
  • 洛谷题单指南-进阶数论-CF776B Sherlock and his girlfriend
  • 下雪了 - L
  • 10/15
  • 2025 印尼物流专线公司推荐榜:聚焦合规高效,深圳恒翔物流凭实力登榜
  • 人文创新研究:在意义的边界探寻新境
  • 平面图最小割与对偶图最短路 - 干
  • 深入解析:Nodejs开发环境搭建
  • 项目管理:PERT/CPM
  • 智能物联网的实时通信之钥——WebSocket
  • 2025 苏州注册公司服务机构实用推荐:选择深度解析
  • 可信AI研究获资助,10位博士生探索算法公平与隐私
  • LeetCode | 45. 跳跃游戏 II(转载)