BZOJ4025 二分图 [线段树分治]

二 分 图 二分图

题目描述见链接 .


正 解 部 分 \color{red}{正解部分}

先考虑如何判断是否 二分图, 可以使用 带权并查集 实现, 具体来说:

加入边 x , y x, y x,y 时, 设其祖先分别为 t 1 t_1 t1, t 2 t_2 t2,

  • t 1 = t 2 t_1 = t_2 t1=t2, 若 d [ x ] ⨁ d [ y ] ⨁ 1 d[x] \bigoplus d[y] \bigoplus 1 d[x]d[y]1奇数, 则直接判断 不为二分图, 否则不将此边加入 .
  • t 1 ≠ t 2 t_1 \not = t_2 t1=t2, 在并查集中, 我们将 t 1 t_1 t1 接到 t 2 t_2 t2 上, t 1 , t 2 t_1,t_2 t1,t2 之间的路径长度 奇偶性 d [ t 1 ] = d [ x ] ⨁ d [ y ] ⨁ 1 d[t_1] = d[x] \bigoplus d[y]\bigoplus 1 d[t1]=d[x]d[y]1 .

再考虑如何维护边的存在, 使用 线段树分治,
每条边都会有一个出现的时间区间 [ L , R ] [L, R] [L,R], 考虑建立下标为 [ 1 , T ] [1, T] [1,T]线段树 ,
将每条边的时间区间 [ L , R ] [L, R] [L,R] 分成 log ⁡ T \log T logT 个区间放在 线段树 的节点中,

然后在 线段树 D F S DFS DFS, 在到一个节点时执行当前节点存储的所有加边操作,

  • 回溯时 栈序 撤回之前操作 .
  • 在向下 d f s dfs dfs 过程中, 若已经出现 奇环, 则直接 r e t u r n return return, 其子树内的答案全部为 No.
  • 若顺利到达 叶子节点, 没有出现 奇环, 则该时刻答案为 Yes .

因为要对 并查集 进行撤回操作, 所以要合并时要 按秩合并 .


实 现 部 分 \color{red}{实现部分}

  • 注意 回溯栈 的数组大小与 线段树 节点 数组大小同阶 .
#include<bits/stdc++.h>
#define reg register
#define pb push_back

int read(){
        char c;
        int s = 0, flag = 1;
        while((c=getchar()) && !isdigit(c))
                if(c == '-'){ flag = -1, c = getchar(); break ; }
        while(isdigit(c)) s = s*10 + c-'0', c = getchar();
        return s * flag;
}

const int maxn = 200005;

int N;
int M;
int Tim;
int F[maxn];
int ds[maxn];
int Rk[maxn];
int Ans[maxn];

struct EDGE{ int u, v; } E[maxn];

struct Stak{ int top; EDGE b[maxn<<2]; } stk;

int Find(int x){ return F[x]==x?x:Find(F[x]); }

int ged(int x){ return F[x]==x?0:ged(F[x])^ds[x]; }

struct Segment_Tree{

        struct Node{ int l, r; std::vector<int> b; } T[maxn << 2];

        void Build(int k, int l, int r){
                T[k].l = l, T[k].r = r;
                if(l == r) return ; int mid = l+r >> 1;
                Build(k<<1, l, mid), Build(k<<1|1, mid+1, r);
        }

        void Add(int k, const int &ql, const int &qr, const int &id){
                int l = T[k].l, r = T[k].r;
                if(r < ql || l > qr) return ;
                if(ql <= l && r <= qr) return T[k].b.pb(id), void();
                Add(k<<1, ql, qr, id), Add(k<<1|1, ql, qr, id);
        }

        void solve(int k){
                int last = stk.top; bool flag = 0;
                for(reg int i = T[k].b.size()-1; ~i; i --){
               //         printf("%d\n", T[k].b[i]);
                        EDGE t = E[T[k].b[i]];
                        int t1 = Find(t.u), t2 = Find(t.v);
                        if(Rk[t1] > Rk[t2]) std::swap(t1, t2);
                        //std::swap(t.u, t.v);
                        int dsu = ged(t.u), dsv = ged(t.v);
                        if(t1 == t2){ if(!(dsu^dsv)) flag = 1; continue ; }
                        F[t1] = t2, ds[t1] = dsu ^ dsv ^ 1; 
                        if(Rk[t1] == Rk[t2]){
                                Rk[t2] ++;
                                stk.b[++ stk.top] = (EDGE){ t1, 1 };
                        }else   stk.b[++ stk.top] = (EDGE){ t1, 0 };
                }
                if(!flag){
                        if(T[k].l == T[k].r) return Ans[T[k].l]=1, void();
                        solve(k<<1), solve(k<<1|1);
                }
                while(1){
                        if(stk.top == last) break ;
                        int top = stk.top --;
                        int x = stk.b[top].u;
                        ds[x] = 0, Rk[F[x]] -= stk.b[top].v, F[x] = x;
                }
        }

} seg_t;

int main(){
        freopen("a.in", "r", stdin);
        freopen("a.out", "w", stdout);
        N = read(), M = read(), Tim = read();
        seg_t.Build(1, 1, Tim);
        for(reg int i = 1; i <= M; i ++){
                E[i].u = read(), E[i].v = read();
                int l = read() + 1, r = read(); //----------------
                seg_t.Add(1, l, r, i);
        }
        for(reg int i = 1; i <= N; i ++) F[i] = i;
        seg_t.solve(1);
        for(reg int i = 1; i <= Tim; i ++) puts(Ans[i]?"Yes":"No");
        return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值