题目:

BZOJ4025

分析:

定理:一个图是二分图的充要条件是不存在奇环。

先考虑一个弱化的问题:保证所有边出现的时间段不会交叉,只会包含或相离。

还是不会?再考虑一个更弱化的问题:边只会出现不会消失。

当加边的时候,若\((u,v)\)不连通:一定不会构成奇环,将它加入。

若\((u,v)\)已经联通,则不加入这条边,而是查询\(u\)和\(v\)两点间的距离。若为偶数则加上这条边后会形成奇环。一个奇环不可能分成数个偶环,所以从此以后都不再是二分图。若为奇数则直接忽略这条边,因为如果将来某条边会与这条边形成奇环,则用当前\(u\)到\(v\)之间的路径(长度为奇数)替代这条边(长度为\(1\))同样也会形成奇环。

按照上述做法,最终我们会造出原图的一棵生成树。

用带权并查集维护连通性和两点间距离的奇偶性。记录每个结点到它并查集上的父亲的距离(注意并查集的形态不一定和原树相同。加入边\((u,v)\)时在并查集上连接的是它们所在集合的根\(f(u)\)和\(f(v)\))。

当加入边\((u,v)\)时,设\(u\)和\(v\)所在并查集的根为\(x\)和\(y\),要把\(x\)合并进\(y\),\(x\)的父亲被赋值成\(y\)。脑补一下此时\(x\)到\(y\)的路径是\(x\)到\(u\),边\((u,v)\),然后\(v\)到\(y\),所以暴力爬链分别求出\(u\)和\(v\)到根的距离,异或起来再异或\(1\)就是\(x\)到它的父亲\(y\)的距离。

查询时把两点到根距离异或起来就是它们之间距离的奇偶性。(树上两点间路径是唯一的,多余的路径被走了偶数次不影响奇偶性。)

如果边会消失呢?删除一条已经被加入的边时,由于保证不存在出现时间交叉的情况,删除的一定是最晚加入的边。因此把对并查集的每次修改都存到一个栈中,撤销的时候按修改顺序的逆序复原即可。这种神奇的数据结构叫“可撤销并查集”

为了保证\(O(\log n)\)的时间复杂度,需要按秩合并。同时由于要可撤销,不能路径压缩破坏树形。放上可撤销并查集的代码。

namespace UFS
{
int fa[N], top, rk[N];
bool dis[N];
struct node
{
int x, y, f, r;
bool d;
}stack[N];
int f(const int x)
{
return x == fa[x] ? x : f(fa[x]);
}
inline void init()
{
for (int i = 1; i <= n; i++)
fa[i] = i, dis[i] = 0, rk[i] = 1;
}
inline bool dist(int x)
{
return x == fa[x] ? dis[x] : dist(fa[x]) ^ dis[x];
}
inline void merge(const int u, const int v)
{
int x = f(u), y = f(v);
if (rk[x] > rk[y])
swap(x, y);
int tmp = fa[x];
stack[top++] = (node){x, y, tmp, rk[y], dis[x]};
if (rk[x] == rk[y])
++rk[y];
dis[x] ^= dist(u) ^ dist(v) ^ 1;
fa[x] = y;
}
inline void undo(const int bck)
{
while (top > bck)
{
fa[stack[top - 1].x] = stack[top - 1].f;
rk[stack[top - 1].y] = stack[top - 1].r;
dis[stack[top - 1].x] = stack[top - 1].d;
--top;
}
}
}

考虑原问题。可以把每条边的出现时间拆成若干个区间,使这些区间互不交叉。然而,如果直接贪心地拆,这些区间的总数会被极端数据卡到\(m^2\)级别(如果所有边出现的区间均为\([k,k+m](1\leq k \leq m)\),则第\(k\)条边将被分成\(k\)个区间,共有\(\frac{m(m+1)}{2}\)个区间)。

这种方式的缺陷在于边的顺序不够灵活。比如上述极端数据中的第\(m\)条边,在\([m+1,2m]\)这个区间中每个时刻都要被删一次再加入一次。如果把它第一个加入(即放在撤销栈的底部),就省了很多操作。

这里引入“线段树分治”。建立一棵线段树,每个结点记录哪些边能完全覆盖这个结点所代表区间(不重复记录。即如果一个结点记录了边\(e\),那么它子树上的结点都不必记录\(e\))。这样每条边最多分成\(\log T\)个区间。最后深搜线段树,搜到一个结点时加入这个结点记录的所有边,回溯时撤销。如果到叶子结点仍然没有出现奇环,则此时刻是二分图。可以参考代码理解。每个边最多被加入或撤销\(\log T\)次,每次加入需要\(\log m\)时间,总复杂度\(O(n\log^2n)\)。

代码:

一个优化:搜到一个结点时如果已经是二分图了,就不必再往下搜。

#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
using namespace std; namespace zyt
{
template<typename T>
inline void read(T &x)
{
bool f = false;
char c;
x = 0;
do
c = getchar();
while (c != '-' && !isdigit(c));
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
inline void write(const char *const s)
{
printf("%s", s);
}
const int N = 1e5 + 10, M = 2e5 + 10, B = 17;
int n, m, T;
namespace UFS
{
int fa[N], top, rk[N];
bool dis[N];
struct node
{
int x, y, f, r;
bool d;
}stack[N];
int f(const int x)
{
return x == fa[x] ? x : f(fa[x]);
}
inline void init()
{
for (int i = 1; i <= n; i++)
fa[i] = i, dis[i] = 0, rk[i] = 1;
}
inline bool dist(int x)
{
return x == fa[x] ? dis[x] : dist(fa[x]) ^ dis[x];
}
inline void merge(const int u, const int v)
{
int x = f(u), y = f(v);
if (rk[x] > rk[y])
swap(x, y);
int tmp = fa[x];
stack[top++] = (node){x, y, tmp, rk[y], dis[x]};
if (rk[x] == rk[y])
++rk[y];
dis[x] ^= dist(u) ^ dist(v) ^ 1;
fa[x] = y;
}
inline void undo(const int bck)
{
while (top > bck)
{
fa[stack[top - 1].x] = stack[top - 1].f;
rk[stack[top - 1].y] = stack[top - 1].r;
dis[stack[top - 1].x] = stack[top - 1].d;
--top;
}
}
}
int ecnt, head[1 << (B + 1)];
bool ans[N];
struct edge
{
int to, next;
}e[M * 18];
struct ed
{
int u, v, st, ed;
}arr[M];
void add(const int a, const int b)
{
e[ecnt] = (edge){b, head[a]}, head[a] = ecnt++;
}
namespace Segment_Tree
{
void insert(const int rot, const int lt, const int rt, const int ls, const int rs, const int x)
{
if (ls <= lt && rt <= rs)
{
add(rot, x);
return;
}
int mid = (lt + rt) >> 1;
if (ls <= mid)
insert(rot << 1, lt, mid, ls, rs, x);
if (rs > mid)
insert(rot << 1 | 1, mid + 1, rt, ls, rs, x);
}
void solve(const int rot, const int lt, const int rt)
{
using namespace UFS;
int bck = top;
bool flag = true;
for (int i = head[rot]; ~i; i = e[i].next)
{
int now = e[i].to, x = f(arr[now].u), y = f(arr[now].v);
if (x == y && !(dist(arr[now].u) ^ dist(arr[now].v)))
{
flag = false;
break;
}
else if (x != y)
merge(arr[now].u, arr[now].v);
}
if (flag)
{
if (lt == rt)
ans[lt] = true;
else
{
int mid = (lt + rt) >> 1;
solve(rot << 1, lt, mid);
solve(rot << 1 | 1, mid + 1, rt);
}
}
undo(bck);
}
} int work()
{
using namespace Segment_Tree;
read(n), read(m), read(T);
UFS::init();
memset(head, -1, sizeof(head));
for (int i = 1; i <= m; i++)
{
read(arr[i].u), read(arr[i].v), read(arr[i].st), read(arr[i].ed);
insert(1, 1, T, arr[i].st + 1, arr[i].ed, i);
}
solve(1, 1, T);
for (int i = 1; i <= T; i++)
if (ans[i])
write("Yes\n");
else
write("No\n");
return 0;
}
}
int main()
{
return zyt::work();
}

【BZOJ4025】二分图(可撤销并查集+线段树分治)的更多相关文章

  1. 【Codeforces576E_CF576E】Painting Edges(可撤销并查集+线段树分治)

    题目 CF576E 分析: 从前天早上肝到明天早上qwq其实颓了一上午MC ,自己瞎yy然后1A,写篇博客庆祝一下. 首先做这题之前推荐一道很相似的题:[BZOJ4025]二分图(可撤销并查集+线段树 ...

  2. 【离线 撤销并查集 线段树分治】bzoj1018: [SHOI2008]堵塞的交通traffic

    本题可化成更一般的问题:离线动态图询问连通性 当然可以利用它的特殊性质,采用在线线段树维护一些标记的方法 Description 有一天,由于某种穿越现象作用,你来到了传说中的小人国.小人国的布局非常 ...

  3. [CSP-S模拟测试]:地理课(并查集+线段树分治)

    题目传送门(内部题146) 输入格式 从$geography.in$读入数据. 第一行两个数$n,m$,表示有$n$个点,$m$个时刻.接下来$m$行每行三个数,要么是$1\ u\ v$,要么是$2\ ...

  4. UVA1455 - Kingdom(并查集 + 线段树)

    UVA1455 - Kingdom(并查集 + 线段树) 题目链接 题目大意:一个平面内,给你n个整数点,两种类型的操作:road x y 把city x 和city y连接起来,line fnum ...

  5. 并查集&线段树&树状数组&排序二叉树

    超级无敌巨牛逼并查集(带权并查集)https://vjudge.net/problem/UVALive-4487 带删点的加权并查集 https://vjudge.net/problem/UVA-11 ...

  6. BZOJ 3910 并查集+线段树合并

    思路: 1. 并查集+线段树合并 记得f[LCA]==LCA的时候 f[LCA]=fa[LCA] 2.LCT(并不会写啊...) //By SiriusRen #include <cstdio& ...

  7. 【xsy2506】 bipartite 并查集+线段树

    题目大意:有$n$个点,你需要操作$m$次.每次操作为加入/删除一条边. 问你每次操作后,这$n$个点构成的图是否是二分图. 数据范围:$n,m≤10^5$. 此题并没有强制在线,考虑离线做法. 一条 ...

  8. bzoj 3237 连通图 - 并查集 - 线段树

    Input Output Sample Input 4 5 1 2 2 3 3 4 4 1 2 4 3 1 5 2 2 3 2 1 2 Sample Output Connected Disconne ...

  9. 并查集 + 线段树 LA 4730 Kingdom

    题目传送门 题意:训练指南P248 分析:第一个操作可以用并查集实现,保存某集合的最小高度和最大高度以及城市个数.运用线段树成端更新来统计一个区间高度的个数,此时高度需要离散化.这题两种数据结构一起使 ...

随机推荐

  1. SqlServer2008必须开启哪些服务

    SQL Server 2008 大概有下面这些服务 SQL Active Directory Helper 服务支持与 Active Directory 的集成SQL Full-text Filter ...

  2. 【Codeforces 364A】Matrix

    [链接] 我是链接,点我呀:) [题意] 让你求出b[i][j]=s[i]*s[j]规则构成的矩阵 的所有子矩阵中子矩阵的和为a的子矩阵的个数 [题解] (x,y,z,t) 会发现它的和就是sum(x ...

  3. 小数化分数的O(log2n)解法

    具体约束: 给定一个小数x,x满足0<=x<1,且保证给定的x保留了18位小数 输出一个分数,使得分母不超过1e9,分子分母互质,且在满足这些条件的情况下最接近x 了解一下法雷数列和ste ...

  4. WordCountPro,完结撒花

    WordCountPro,完结撒花 软测第四周作业 一.概述 该项目github地址如下: https://github.com/YuQiao0303/WordCountPro 该项目需求如下: ht ...

  5. Leetcode 135.分糖果

    分发糖果 老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分. 你需要按照以下要求,帮助老师给这些孩子分发糖果: 每个孩子至少分配到 1 个糖果. 相邻的孩 ...

  6. image url to base64

    image url to base64 https://www.base64-image.de/ https://www.browserling.com/tools/image-to-base64 h ...

  7. SAP Portal 上传资源到WRR

    Uploading Resources to the Web Resource Repository Prerequisites You have been assigned the Content ...

  8. ms sql SERVER 下载

    企业版DVD SQL Server 2005 Enterprise Edition(支持超大型企业) 32 位DVD: ed2k://|file|cs_sql_2005_ent_x86_dvd.iso ...

  9. react实现ssr服务器端渲染总结和案例(实例)

    1.什么是 SSR SSR 是 server side render 的缩写,从字面上就可以理解 在服务器端渲染,那渲染什么呢,很显然渲染现在框架中的前后端分离所创建的虚拟 DOM 2.为什么要实现服 ...

  10. php apc缓存以及与redis的对比

    apc缓存用户数据这个会用, apc_store() 和apc_fetch()函数. 那apc 缓存php 代码,这个怎么用呢? 原文: http://blog.csdn.net/w187046226 ...