【题解】CF603E | 半在线线段树分治
题意翻译的很清楚,不再叙述。
有一个结论就是 “偶数度连通块是有解的充分必要条件” ,很多题解说的都是“调整法”,便于理解,但给出一个具体化的证明,如下:
答案是生森林是显然的,因为环一定可以从集合中删去使得答案更优
首先,奇数大小的连通块的边数为偶数,每条边对总度数的贡献是 2,而总度数等于 奇数 * 奇数 仍然为奇数,故是必要条件。
对于充分性,只要给出一组构造即可。
令 表示大小为 2x 的连通块。
对于大小为 0 的连通块 空集为解。
对于大小为 2 的连通块 有解,直接选择二点之间的边即有解。
对于 若 有解,则 有解。
,证明如下:
因为该连通块的大小是偶数,所以该连通块一定可以被划分成两个大小为偶数的连通块,或两个大小为奇数的连通块。
若该连通块可以被约化为 两边都有解。
若该连通块不能被划分成两个偶数大小的连通块,则任意选择一条边,将这条边加入边集,钦定这两个点不再被其他边连接,可以约化成 。
故 若 有解,则 有解。
由数学归纳法, 有解,又因上定理,故偶数大小是成立的充分必要条件。
回到本题,已经解决静态问题了,考虑“加边”怎么处理。
将“加边”离线,然后由于答案是单调不降的,选的边只会越来越大,考虑倒着计算,将边按权值排序,先按顺序加入边直道答案成立(用一个指针从左向右加—),知道该边的起始时间的前一个时刻把这条边删了,继续加入边即可。
但是由于“删边”很难实现,又因为和图的连通性有关,于是考虑用一个半在线的线段树分治去维护它,记录奇数大小的连通块数,使用可撤销并查集,先遍历右儿子,遍历到叶节点就判断,并向前面区间加边即可。
一个坑点是不能在 直接连边( 表示这条边的出现时间, 表示目前分治到的时间点) 因为已经遍历到了这个叶子,可能导致上面的标记没有生效,应在 连边。
线段树分治部分代码:
/*uniset 表示合并 , disset表示拆分 ,O记录操作以撤回 , cnt表示目前奇数大小的连通块数*/
/*本人码风、缩进略怪,多多包涵*/
void solve(int x,int l,int r) {
vector<Pair> O;
for(int p : ed[x]) {
int U = find(e[p].u) , V = find(e[p].v) ;
if(U^V) {
if(siz[U] < siz[V]) swap(U,V) ;
uniset(U,V); O.emplace_back(U,V);
}
}
if(l == r) {
while (cnt && ptr <= m) {
if(e[ptr].tim <= l)
{
int U = find(e[ptr].u) , V = find(e[ptr].v) ;
if(U^V) {
if(siz[U] < siz[V]) swap(U,V) ;
uniset(U,V); O.emplace_back(U,V);
}
adde(1,1,m,e[ptr].tim,l - 1,ptr); /*坑点就是这里!!!*/
}
ptr ++ ;
}
if(!cnt) ans[l] = e[ptr - 1].w; else ans[l] = -1 ;
}
else {
int mid = l + r >> 1;
solve(x<<1|1,mid+1,r);
solve(x<<1,l,mid) ;
}
reverse(All(O)) ;
for(auto[U,V]:O) disset(U,V) ;
}
(有快读之类的东西,define极多,观感不佳,上方代码为方便阅读进行了缩进修改,删除了部分注释。)
如有笔误、逻辑错误欢迎轻喷。
感谢您的阅读,感谢管理员辛苦审核。
本文已经结束了。本文作者:ღꦿ࿐(DeepSea),转载请注明原文链接:https://www.cnblogs.com/Dreamerkk/p/17970961,谢谢你的阅读或转载!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步