【oSo的题解】题解:P5787 二分图 /【模板】线段树分治
题目:P5787 二分图 /【模板】线段树分治
前言
一个挺简洁巧妙的离线(是的强制在线用不了)小技巧,我竟然看懂了。
题意
给出 \(n\) 个点,\(m\) 条出现时间为 \((l,r]\) 的边,求在 \([1,k]\) 每一时刻,该图是否为二分图。
分析
前置芝士:扩展域并查集(种类并查集)、可撤销并查集(本题不需要可持久化并查集哦)、线段树。
Part-0
首先先说一下如何判断二分图。
我们先开一下每个点的反集,即对于一个点 \(u\),所有与 \(u\) 相连的点 \(v\),都在 \(u+n\) 这个集合里。
然后每加入一条边时,考虑到只有边的两个顶点可能会发生冲突,就 find 一下是否在同一集合中,不在就相互加反集,在就没有必要继续处理了直接往回撤销。
Part-1
那该怎么维护加边和删边呢?
注意到我们不喜欢删边,会造成极大的时间开销,于是可以通过撤销上一次合并操作来达成删边的目的。
而加边就是普通的合并操作。
因为要求我们输出每一时刻的答案,我们可以沿着时间轴来处理询问。
Part-2
如何维护对于某一时间戳上的所有操作?
我们观察到如果只是普通地从前向后扫时间轴,拿到了下一个点及不知道该如何撤销,又不知道该加什么边。
我们可以考虑树形的数据结构。
我们可以对于每一时刻认为是一个独立的叶子节点,然后建线段树。
线段树每一节点都储存着一些操作,这些操作的出现时间均包含该节点的时间段,即相当于每一个节点上的 tag 记录了这一时间段存在的边。
然后我们可以通过类似于区间加的方式,将每一段的出现时间打在线段树上。
Part-3
那我们如何获取答案呢?
观察到,对于每一时刻的答案,即每个叶子的答案,是判断从线段树的根节点到该节点的路径上所有经过点包含的 tag 打在一起的图是否为二分图。
那么我们考虑从根节点进行 dfs,每到一个点就根据 Part-0 判断到目前为止的图是否为二分图,如果不是就没有搜的必要了,可以直接回溯。
代码细节提醒
要注意可撤销并查集只能写按秩合并,不要写路径压缩。
注意当不是二分图需要回溯时一定要先撤销再回溯。
撒花。
AC Code
#include<bits/stdc++.h>
#define int long long 
#define rep(I,A,B) for(int I=(A);I<=(B);++I)
#define per(I,A,B) for(int I=(A);I>=(B);--I)
#define el puts("")
#define Yuki return 
#define daisuki 0
using namespace std;
using pii=pair<int,int>;
//码风丑还请见谅捏
//代码已经被我分成了小部分,可以按照每个部分进行debug
//不要直接复制粘贴我的代码谢谢哈
//fastIO start
template<typename T>
void read(T &x){
  x=0;bool f=0;char c=getchar();
  for(;!isdigit(c);c=getchar())if(c=='-')f=1;
  for(;isdigit(c);c=getchar())x=(x<<3)+(x<<1)+(c^48);
  if(f)x=-x;
}
template<typename T,typename ...Args>
void read(T &x,Args &...args){
  read(x),read(args...);
}
template<typename T>
void write(T x,char ch){
  if(x<0)putchar('-'),x=-x;
  static short st[70],tp;
  do st[++tp]=x%10,x/=10;while(x);
  while(tp)putchar(st[tp--]|48);
  putchar(ch);
}
//fastIO end
//some variable start
const int N=1e5+5;
const int M=2e5+5;
int n,m,k;
int fa[N<<1],siz[N<<1],stk[N<<1],tp;
struct Seg{
  vector<pii> tag;
}tr[N<<2];
//some variable end
//DS start
  //Disjoint Sets Union start
  int find(int x){return x==fa[x]?x:find(fa[x]);}//不要写路径压缩捏
  void merge(int x,int y){
    int p=find(x),q=find(y);
    if(p==q)return ;
    if(siz[p]<siz[q])swap(p,q);
    fa[q]=p;
    siz[p]+=q;
    stk[++tp]=q;
  }
  void back(int lst){while(tp>lst){int y=stk[tp--];if(y==fa[y])continue;siz[fa[y]]-=siz[y],fa[y]=y;}}
  //Disjoint Sets Union end
  //Segment Tree start
  void modify(int p,int L,int R,pii k,int l,int r){
    if(r<L||l>R)return ;
    if(L<=l&&r<=R)return tr[p].tag.emplace_back(k),void();
    int mid=l+r>>1;
    modify(p<<1,L,R,k,l,mid);
    modify(p<<1|1,L,R,k,mid+1,r);
  }
  void dfs(int p,int l,int r){
    int nowtp=tp,mid=l+r>>1;
    bool f=0;
    for(auto i:tr[p].tag){
      int p=find(i.first),q=find(i.second);
      if(p==q){
        rep(i,l,r)puts("No");f=1;break;
      }
      merge(i.first,i.second+n),merge(i.first+n,i.second);
    }
    if(f)return back(nowtp),void();//一定要先back再return
    if(l==r)puts("Yes");
    else dfs(p<<1,l,mid),dfs(p<<1|1,mid+1,r);
    back(nowtp);
  }
  //Segment Tree end
//DS end
void input(){
  read(n,m,k);
  rep(i,1,n<<1)fa[i]=i,siz[i]=1;//要注意开反集时要2倍
  rep(i,1,m){
    int u,v,l,r;
    read(u,v,l,r);
    modify(1,l+1,r,{u,v},1,k);//区间加边
  }
}
void solve(){
  dfs(1,1,k);
}
signed main(){
  input();
  solve();
  Yuki daisuki;//有希真的是太可爱了捏
}
//by oSomiwww
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号