• 【做题】cf603E——线段树分治


    首先感谢题解小哥,他在标算外又总结了三种做法。
    此处仅提及最后一种做法。
    首先考虑题目中要求的所有结点度数为奇数的限制。
    对于每一个联通块,因为所有结点总度数是偶数,所以总结点数也必须是偶数的。即所有联通块都要是偶数大小。
    而考虑任意一个偶数大小的联通块,我们任意取它的一个生成树,然后进行如下算法:

    设 1 为根结点;
    按深度从大到小枚举每一个结点
    若其当前度数为偶数
    则断开与他的父结点的连边;

    这样除根结点外的所有结点的度数都能保证为奇数,而因为总度数和为偶数,所以根结点的度数也为奇数。
    因此,我们得到

    存在方案使得所有结点度数为奇数 (iff) 所有联通快大小为偶数。

    注意到偶数加偶数还是偶数,换言之,添加多余的边是不会使答案变劣的。并且,答案是单调递减的。所以我们可以达到如下结论:

    如果第(j)次询问的答案大于等于第(i)条边的边权,那么可以在处理询问区间(left[ i,j-1 ight])时直接将第(i)条边加上。

    这样我们就可以用线段树分治。我们对询问开线段树,从后往前处理。遍历到叶结点时按边权暴力从小到大枚举边(在上一次基础上),与此同时确定了枚举到的边产生贡献的范围,用线段树实现区间修改。在遍历时需要维护支持撤销操作的并查集。这相当于是在分治的同时确定每条边的删除时间,即答案小于它的边权的时刻。
    时间复杂度(O(nlog^2n))

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 300010;
    int odd;
    struct record {
      int *p,v;
      inline void rollback() {
        *p = v;
      }
    } rec[N * 10];
    int uni[N],sz[N],cnt;
    int get_fa(int x) {
      while (uni[x] != x)
        x = uni[x];
      return x;
    }
    void unio(int x,int y) {
      x = get_fa(x);
      y = get_fa(y);
      if (x == y) return;
      if (sz[x] > sz[y]) swap(x,y);
      int tmp = (sz[x]&1) + (sz[y]&1) - ((sz[x] + sz[y])&1);
      rec[++cnt] = (record) {&odd,odd};
      odd -= tmp;
      rec[++cnt] = (record) {&uni[x],uni[x]};
      uni[x] = y;
      rec[++cnt] = (record) {&sz[y],sz[y]};
      sz[y] += sz[x];
    }
    struct data {
      int a,b,v,id;
      bool operator < (const data& x) const {
        return v < x.v;
      }
    } dat[N];
    vector<int> edg[N << 2];
    int n,m,cur,ans[N];
    void modify(int lp,int rp,int id,int x,int l,int r) {
      if (lp > rp) return;
      if (lp > r || rp < l) return;
      if (l >= lp && r <= rp)
        return (void) (edg[x].push_back(id));
      int mid = (l + r) >> 1;
      modify(lp,rp,id,x<<1,l,mid);
      modify(lp,rp,id,x<<1|1,mid+1,r);
    }
    void solve(int x,int l,int r) {
      int tmp = cnt;
      for (int i = 0 ; i < (int)edg[x].size() ; ++ i)
        unio(dat[edg[x][i]].a,dat[edg[x][i]].b);
      if (l != r) {
        int mid = (l + r) >> 1;
        solve(x<<1|1,mid+1,r);
        solve(x<<1,l,mid);
      } else {
        for ( ; cur <= m && odd > 0 ; ++ cur) {
          if (dat[cur].id > l) continue;
          unio(dat[cur].a,dat[cur].b);
          modify(dat[cur].id,l-1,cur,1,1,m);
        }
        if (odd > 0) ans[l] = -1;
        else ans[l] = dat[cur-1].v;
      }
      while (cnt > tmp)
        rec[cnt--].rollback();
    }
    int main() {
      int a,b,c;
      scanf("%d%d",&n,&m);
      odd = n;
      for (int i = 1 ; i <= m ; ++ i) {
        scanf("%d%d%d",&a,&b,&c);
        dat[i] = (data) {a,b,c,i};
      }
      for (int i = 1 ; i <= n ; ++ i)
        uni[i] = i, sz[i] = 1;
      sort(dat+1,dat+m+1);
      cur = 1;
      solve(1,1,m);
      for (int i = 1 ; i <= m ; ++ i)
        printf("%d
    ",ans[i]);
      return 0;
    }
    

    小结:其实我根本不会想到糊结论……线段树分治的做法,相比LCT做法更加巧妙,利用题目的特殊性质从而简化了代码量。
  • 相关阅读:
    curl 抓取图片
    checkbox 全选
    大文件断点上传 js+php
    php快速排序
    直接插入排序(Straight Insertion Sort)
    选择排序 Selection sort
    57.猴子吃桃问题
    56.一球从100米高度自由落下,每次落地后反跳回原高度的一半;再落下,求它在第10次落地时,共经过多少米?第10次反弹多高?
    55.输入两个正整数m和n,求其最大公约数和最小公倍数
    54.将一个正整数分解质因数。例如:输入90,打印出90=2*3*3*5
  • 原文地址:https://www.cnblogs.com/cly-none/p/8978818.html
Copyright © 2020-2023  润新知