[Codeforces 603E]Pastoral Oddities(LCT)

[Codeforces 603E]Pastoral Oddities(LCT)

题面

图中有n个孤立点,依次向图中加入m条带权无向边。使得图中每个点的度数均为奇数的边集是合法的,其权值定义为集合中的最大边权。每次加入边后,询问权值最小的合法边集的权值,不存在合法边集时输出?1。

\(n \leq 10^5,m \leq 3\times 10^5\)

分析

手玩样例可得:图存在合法边集,当且仅当每个连通块的大小为偶数

证明:

先证明充分性:假设某合法连通块大小为奇数,那么该块的总度数是奇数。但所有点的度数之和一定为偶数(因为一条边会被算入2个点的度数)。所以不存在合法边集。因此连通块大小一定为偶数。

再证明必要性: 求出连通块的任意一棵生成树。从下往上处理每个非根节点\(x\).如果\(x\)到它生成树中的孩子的边中,已经有偶数条被选入边集。那么把\(x\)到\(fa(x)\)的边加入边集,这样\(x\)的度数就是奇数。对于根节点,因为刚刚我们已经把非根节点的度数全部设为奇数,但所有点的度数之和为偶数,那么根节点的度数一定是奇数。

注意到充分性的证明已经给出了一种答案构造方法。这个方法中选择的边一定是原来连通块生成树上的。而且原来的连通块被划分成了更小的连通块(当\(x\)没有连到\(fa(x)\)时),每个新联通块的最小生成树就是权值最小的合法边集。

我们用一个优先队列来维护森林中的每条边,按权值从小到大排序。实现上由于priority_queue的一些限制,我们改用set< pair<int,int> >.其中第一关键字为边权,第二关键字为边的编号

那么我们就可以用LCT维护这个生成树森林。

考虑每次加边操作\((u,v,w)\)的影响。

  1. 如果\(u,v\)已经连通,那么我们就维护最小生成树,查找\(u\)到\(v\)路径上最大的边权\(w_0\).

    若\(w_0>w\),那么就把生成树上的\(w_0\)换成\(w\).同时更新set.

    否则,先判断当前是否有解(是否存在度数为奇数的联通块),有解输出set中的最大边权。

  2. 如果\(u,v\)不连通,我们将新的边加入LCT和set.并且更新度数为奇数的联通块个数。
  3. 如果生成树的边有变化,我们需要重新计算最优答案。从set中最大的边开始,检查能否移除这条边。若能移除则移除并检查次大,否则结束。

那么我们的LCT除了基本操作之外,要支持:

  1. 查询路径上边权最大的边。类似[BZOJ2594] [WC2006]水管局长(Kruskal+LCT)那样把边看成点即可。
  2. 查询生成树上某个点的子树大小.类似[BJOI2014]大融合(Link Cut Tree)维护虚子树信息即可。注意由于我们把边看成了点,一个点数为\(p\)的子树在LCT中变成了\(p+(p-1)=2p-1\)。那么真正的点数就是\((sz[p]+1)/2\)

复杂度分析:LCT操作的复杂度为\(O((n+m) \log n)\)。每条边只会在set中被删除一次,复杂度\(O(m \log m)\)。

总结:这题先是利用了一个美妙的性质将问题转化为生成树问题,然后又结合了LCT维护生成树和维护子树信息两种套路。既有思维难度又有数据结构难度。给出题人点赞!

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<utility>
#define maxn 100000
#define maxm 300000
using namespace std;
int n,m;
struct edge {
    int from;
    int to;
    int len;
} E[maxm+5];

struct LCT {
#define fa(x) (tree[x].fa)
#define lson(x) (tree[x].ch[0])
#define rson(x) (tree[x].ch[1])
    struct node {
        int ch[2];
        int fa;
        int sz;//原树上的子树大小
        int vsz;//虚子树大小
        int id;
        int maxid;
        int revm;
    } tree[maxn+maxm+5];
    inline bool is_root(int x) {
        return !(lson(fa(x))==x||rson(fa(x))==x);
    }
    inline int check(int x) {
        return rson(fa(x))==x;
    }
    void push_up(int x) {
        tree[x].sz=tree[lson(x)].sz+1+tree[rson(x)].sz+tree[x].vsz;
        tree[x].maxid=tree[x].id;
        if(E[tree[lson(x)].maxid].len>E[tree[x].maxid].len) tree[x].maxid=tree[lson(x)].maxid;
        if(E[tree[rson(x)].maxid].len>E[tree[x].maxid].len) tree[x].maxid=tree[rson(x)].maxid;
    }
    void reverse(int x) {
        swap(lson(x),rson(x));
        tree[x].revm^=1;
    }
    void push_down(int x) {
        if(tree[x].revm) {
            reverse(lson(x));
            reverse(rson(x));
            tree[x].revm=0;
        }
    }
    void push_down_all(int x) {
        if(!is_root(x)) push_down_all(fa(x));
        push_down(x);
    }
    void rotate(int x) {
        int y=fa(x),z=fa(y),k=check(x),w=tree[x].ch[k^1];
        tree[y].ch[k]=w;
        tree[w].fa=y;
        if(!is_root(y)) tree[z].ch[check(y)]=x;
        tree[x].fa=z;
        tree[x].ch[k^1]=y;
        tree[y].fa=x;
        push_up(y);
        push_up(x);
    }
    void splay(int x) {
        push_down_all(x);
        while(!is_root(x)) {
            int y=fa(x);
            if(!is_root(y)) {
                if(check(x)==check(y)) rotate(y);
                else rotate(x);
            }
            rotate(x);
        }
        push_up(x);
    }
    void access(int x) {
        for(int y=0; x; y=x,x=fa(x)) {
            splay(x);
            tree[x].vsz-=tree[y].sz-tree[rson(x)].sz;//把原来是虚子树,现在是实的减掉
            rson(x)=y;
            push_up(x);
        }
    }
    void make_root(int x) {
        access(x);
        splay(x);
        reverse(x);
    }
    void split(int x,int y) {
        make_root(x);
        access(y);
        splay(y);
    }
    void link(int x,int y) {
        make_root(x);
        fa(x)=y;
        tree[y].vsz+=tree[x].sz;
        push_up(y);
    }
    void cut(int x,int y) {
        split(x,y);
        fa(x)=lson(y)=0;
        push_up(y);
    }
    void add_edge(int id) {
        link(E[id].from,id+n);
        link(E[id].to,id+n);
    }
    void del_edge(int id) {
        cut(E[id].from,id+n);
        cut(E[id].to,id+n);
        tree[id+n].vsz=0;
    }
    int find_root(int x) {
        access(x);
        splay(x);
        while(lson(x)) x=lson(x);
        return x;
    }

    int query_route(int x,int y) {
        split(x,y);
        return tree[y].maxid;
    }
} T;
set< pair<int,int> >ed;//按边权从小到大存储生成树中的边,first为边权,second为编号
inline int is_odd(int x) {
    //判断x所在联通块的点数是否为奇数
    //LCT里p个点,p-1条边,sz[x]=2p-1
    //真正的点数为(sz[x]+1)/2
    return ((T.tree[x].sz+1)/2)%2==1;
}
void build() {
    while(1) {
        int id=(--ed.end())->second;
        int x=E[id].from;
        int y=E[id].to;
        T.split(x,y);
        if(is_odd(x)) break;
        T.del_edge(id);
        ed.erase(*(--ed.end()));
    }
}
int main() {
    scanf("%d %d",&n,&m);
    for(int i=1; i<=n+m; i++) {
        if(i<=n) T.tree[i].id=0;
        else T.tree[i].id=i-n;
        T.push_up(i);
    }
    for(int i=1; i<=m; i++) scanf("%d %d %d",&E[i].from,&E[i].to,&E[i].len);
    int odd_sz_cnt=n;
    for(int i=1; i<=m; i++) {
        int u=E[i].from,v=E[i].to,w=E[i].len;
        if(T.find_root(u)==T.find_root(v)) {
            int now=T.query_route(u,v);
            if(w<E[now].len) {
                T.del_edge(now);
                T.add_edge(i);
                ed.erase(make_pair(E[now].len,now));
            } else {
                if(odd_sz_cnt>1) printf("-1\n");
                else printf("%d\n",(--ed.end())->first);//输出最大边
                continue;
            }
        } else {
            T.make_root(u);
            odd_sz_cnt-=is_odd(u);
            T.make_root(v);
            odd_sz_cnt-=is_odd(v);
            //把原来的两个联通块的贡献减掉
            T.add_edge(i);
            T.make_root(u);
            odd_sz_cnt+=is_odd(u);
            //再加回新的贡献
        }
        ed.insert(make_pair(w,i));
        if(odd_sz_cnt>0) {
            printf("-1\n");
            continue;
        }
        build();//构造出联通块里选择的边集
        printf("%d\n",(--ed.end())->first);
    }
}

原文地址:https://www.cnblogs.com/birchtree/p/12207725.html

时间: 2024-10-14 09:33:33

[Codeforces 603E]Pastoral Oddities(LCT)的相关文章

Codeforces 603E Pastoral Oddities

传送门:http://codeforces.com/problemset/problem/603/E [题目大意] 给出$n$个点,$m$个操作,每个操作加入一条$(u, v)$长度为$l$的边. 对于每次操作后,求出一个边集,使得每个点度数均为奇数,且边集的最大边最小. $n \leq 10^5, m \leq 3 * 10^5$ [题解] 有结论:满足条件(每个点度数均为奇数),当且仅当每个连通块大小都是偶数(容易证明,从下往上,调整法). 那么显然可以LCT维护连通性,连通块大小以及最大边

【CF603E】Pastoral Oddities cdq分治+并查集

[CF603E]Pastoral Oddities 题意:有n个点,依次加入m条边权为$l_i$的无向边,每次加入后询问:当前图是否存在一个生成子图,满足所有点的度数都是奇数.如果有,输出这个生成子图中边权最大的边的权值最小可能是多少. $n\le 10^5,m\le 10^6,l_i\le 10^9$ 题解:可以证明如果存在一个生成子图满足所有点度数都是奇数,当且仅当所有连通块都有偶数个点.并且可以知道加边一定不会使答案更劣.正解有三种:1.LCT维护最小生成树:2.cdq分治(类似整体二分)

Codeforces603E - Pastoral Oddities

Portal Description 初始时有\(n(n\leq10^5)\)个孤立的点,依次向图中加入\(m(m\leq3\times10^5)\)条带权无向边.使得图中每个点的度数均为奇数的边集是合法的,其权值定义为集合中的最大边权.每次加入边后,询问权值最小的合法边集的权值,不存在合法边集时输出\(-1\). Solution 存在合法边集 \(\Leftrightarrow\) 每个连通块的大小均为偶数.如果某连通块大小为奇数,那么该块的总度数是奇数,但一条无向边会提供两个度数,所以不存

CodeForces gym Nasta Rabbara lct

Nasta Rabbara 题意:简单来说就是, 现在有 n个点, m条边, 每次询问一个区间[ l ,  r ], 将这个区间的所有边都连上, 如果现在的图中有奇数环, 就输出 "Impossible", 否者就输出 "possible". 题解: 步骤1:我们先找出每个最小的 [ l,  r]  当这个区间的边都出现后, 就会出现一个奇数环. 步骤2:问题就变成了对于一次询问 [ L, R ]  是否存在上面的一个区间 被完全覆盖. 对于步骤1来说:需要加边和删

cf Round 603

A.Alternative Thinking(思维) 给出一个01串,你可以取反其中一个连续子串,问取反后的01子串的最长非连续010101串的长度是多少. 我们随便翻一个连续子串,显然翻完之后,对于这个连续子串而言,最后的答案一定不会变优.只会对你翻的左端点和右端点相邻的数字产生贡献.我们计左端点为l,右端点为r.而且要想最大化贡献,必须要使得这个a[l]和a[l-1]一样.a[r]和a[r+1]一样.那么我们只要找到可以使这个贡献获得最大时的条件就行了. # include <cstdio>

Codeforces 482E ELCA (LCT)

题目链接 http://codeforces.com/contest/482/problem/E 题解 T2智商题T3大LCT题,我一个也不会= = CF的标算好像是分块?反正现在LCT都普及了就用LCT好了. 首先算期望推个式子,易得答案为\(\sum_u a[u](sz[u]^2-\sum_{v\in son[u]} sz[v]^2)\) (\(sz\)为子树大小),令求和的那个东西等于\(f[u]\) 并且如果往一个\(u\)里新添一个儿子\(v\),增添后的子树大小是\(sz[v]\),

codeforces round #321 (div2)

codeforces#321(div2) A题:水题. #include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) using namespace std; typedef long long ll; const int maxn=1000100; ll n,a[maxn]; int main() { //freopen("in.txt","r",stdin); while(ci

@codeforces - [email&#160;protected] Mashmokh&#39;s Designed Problem

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一棵 n 个点的树,每个点的儿子是有序的. 现给定 m 次操作,每次操作是下列三种中的一种: (1)给定 u, v,询问 u, v 之间的距离. (2)给定 v, h,断开 v 到父亲的边,将 v 这棵子树加入到它的第 h 个祖先的最后一个儿子. (3)给定 k,询问在当前这棵树上

【codeforces 718E】E. Matvey&#39;s Birthday

题目大意&链接: http://codeforces.com/problemset/problem/718/E 给一个长为n(n<=100 000)的只包含‘a’~‘h’8个字符的字符串s.两个位置i,j(i!=j)存在一条边,当且仅当|i-j|==1或s[i]==s[j].求这个无向图的直径,以及直径数量. 题解:  命题1:任意位置之间距离不会大于15. 证明:对于任意两个位置i,j之间,其所经过每种字符不会超过2个(因为相同字符会连边),所以i,j经过节点至多为16,也就意味着边数至多