codeforces 603E Pastoral Oddities 题解

大家好,我是题解搬运工

http://blog.csdn.net/u014609452/article/details/54020829

https://post.icpc-camp.org/d/324-codeforces-round-334-e-pastoral-oddities

介于题解较少,且对于我来说比较难理解,于是决定自己重新写一遍题解

 

题目大意,选前i条边中的一些边,要求每个点的度都为奇数,且最长边最短。

这道题真是非常难想,首先我们考虑静态的做法。先给出结论,按照边的长短从小到大加入,直到每个联通块的大小都为偶数为止。

如果一个联通块的节点的个数为偶数,那么我们一定可以在联通块中删除一些边,使得其中每个点的度数都为奇数。(我们从叶子节点开始,逐一排查,不行就删边,反正这个结论挺难想的QAQ)

知道这个结论我们就好办了,题目变为用尽量少的边使得每个联通块点数为奇数。下面根据第一个题解,有3种做法

 

1.LCT

LCT 维护子树异或和与最大值(经典题)

每次操作,考虑第i跳边,x,y,如果xy不连通,则连通,否则看它们的路径上最大的边的权值和当前v的大小,若大于,则删边,加边

在每次加边删边的时候,通过子树大小异或和动态维护此时状态,比较经典的LCT题吧

  1 //start:   finish:
  2 #include<bits/stdc++.h>
  3 #define maxn 400005
  4 using namespace std;
  5 int n,m;
  6 int odd;
  7 bool rev[maxn];
  8 int c[maxn][2],fa[maxn];
  9 struct node{
 10     int mx,val;
 11     int sub,all,zhi;
 12 }a[maxn];
 13 pair <int,int> bian[maxn];
 14 set <pair<int,int> > s;
 15 int st[maxn];
 16 bool isroot(int x){
 17     return (c[fa[x]][0]!=x && c[fa[x]][1]!=x);
 18 }
 19 int MAX(int x,int y){
 20     if(!x || !y)return x+y;
 21     if(a[x].val>a[y].val)return x;
 22     return y;
 23 }
 24 void update(int x){
 25     a[x].all=a[x].sub^a[x].zhi;
 26     if(c[x][0])a[x].all^=a[c[x][0]].all;
 27     if(c[x][1])a[x].all^=a[c[x][1]].all;
 28     a[x].mx=x;
 29     a[x].mx=MAX(a[x].mx,a[c[x][0]].mx);
 30     a[x].mx=MAX(a[x].mx,a[c[x][1]].mx);
 31 }
 32 void down(int x){
 33     if(rev[x]){
 34         rev[c[x][0]]^=1;
 35         rev[c[x][1]]^=1;
 36         rev[x]^=1;
 37         swap(c[x][0],c[x][1]);
 38     }
 39 }
 40 void rotate(int x){
 41     int y=fa[x],z=fa[y];
 42     int l,r;
 43     if(c[y][0]==x)l=0;else l=1;
 44     r=1-l;
 45     if(!isroot(y)){
 46         if(c[z][0]==y)c[z][0]=x;
 47         else c[z][1]=x;
 48     }
 49     fa[x]=z;fa[y]=x;fa[c[x][r]]=y;
 50     c[y][l]=c[x][r];c[x][r]=y;
 51     update(y);update(x);
 52 }
 53 void splay(int x){
 54     int top=0;st[++top]=x;
 55     for(int i=x;!isroot(i);i=fa[i])st[++top]=fa[i];
 56     for(int i=top;i;i--)down(st[i]);
 57     while(!isroot(x)){
 58         int y=fa[x],z=fa[y];
 59         if(!isroot(y)){
 60             if(c[y][0]==x^c[z][0]==y)rotate(x);
 61             else rotate(y);
 62         }
 63         rotate(x);
 64     }
 65 }
 66 void access(int x){
 67     int t=0;
 68     while(x){
 69         splay(x);
 70         down(x);
 71         if(t)a[x].sub^=a[t].all;
 72         if(c[x][1])a[x].sub^=a[c[x][1]].all;
 73         c[x][1]=t;
 74         update(x);
 75         t=x;
 76         x=fa[x];
 77     }
 78 }
 79 void move_to_root(int x){
 80     access(x);splay(x);rev[x]^=1;
 81 }
 82 int find(int x){
 83     access(x);splay(x);
 84     while(c[x][0])x=c[x][0];
 85     return x;
 86 }
 87 void split(int x,int y)
 88 {
 89     move_to_root(y);
 90     access(x);
 91     splay(x);
 92 }
 93 void link(int x,int y){
 94     move_to_root(x);
 95     move_to_root(y);
 96     fa[x]=y;a[y].sub^=a[x].all;
 97     update(y);
 98 }
 99 void cut(int x,int y){
100     move_to_root(x);
101     access(y);splay(y);
102     c[y][0]=0;fa[x]=0;
103     update(y);
104 }
105 void Link(int o){
106     int x=bian[o].first,y=bian[o].second;
107     move_to_root(x);move_to_root(y);
108     if(a[x].all && a[y].all)odd-=2;
109     link(x,o+n);
110     link(y,o+n);
111 }
112 void Cut(int o){
113     int x=bian[o].first,y=bian[o].second;
114     cut(x,o+n);cut(y,o+n);
115 }
116 bool shan(int o){
117     int x=bian[o].first,y=bian[o].second;
118     cut(x,o+n);cut(y,o+n);
119     move_to_root(x);move_to_root(y);
120     if(!a[x].all && !a[y].all)return true;
121     link(x,o+n);link(y,o+n);
122     return false;
123 }
124 int main(){
125     scanf("%d%d",&n,&m);
126     odd=n;
127     for(int i=1;i<=n;i++){
128         a[i].all=a[i].zhi=1;
129     }
130     for(int i=1;i<=m;i++){
131         int x,y,z;
132         scanf("%d%d%d",&x,&y,&z);
133         a[i+n].val=z;a[i+n].mx=i+n;
134         bian[i]=make_pair(x,y);
135         if(find(x)!=find(y)){
136             Link(i);
137             s.insert(make_pair(-z,i));
138         }
139         else{
140             split(x,y);int e=a[x].mx;
141             if(a[e].val>z){
142                 Cut(e-n);
143                 link(i+n,x);
144                 link(i+n,y);
145                 s.erase(make_pair(-a[e].val,e-n));
146                 s.insert(make_pair(-z,i));
147             }
148         }
149         if(odd==0){
150             while(true){
151                 int e=(*s.begin()).second;
152                 if(shan(e)){
153                     s.erase(s.begin());
154                 }
155                 else break;
156             }
157         }
158         if(odd)puts("-1");
159         else printf("%d\n",-(*s.begin()).first);
160     }
161     return 0;
162 }
LCT

 

2.CDQ分治

solve(l,r,ll,rr)代表处理(l,r)的询问,且答案在(ll,rr)之内的情况,然后分治递归,可持久化堆。每次算出mid=(l+r)/2的答案nmid

那么(l,mid-1)的答案在(nmid,r)之间,(mid+1,r)的答案在(l,nmid)之间,递归下去即可

//start:   finish:
#include<bits/stdc++.h>
#define maxn 300005
using namespace std;
int n,m;
int ans[maxn];
struct DUI
{
    int last,tot;
    int fa[maxn],sz[maxn];
    int p[maxn],q[maxn];
    int odd;
    int find(int x){
        if(fa[x]==x)return x;
        return find(fa[x]);
    }
    void bei(){
        last=tot;
    }
    void merge(int x,int y){
        if(find(x)==find(y))return;
        x=find(x);y=find(y);
        if(sz[x]%2==1 && sz[y]%2==1)odd-=2;
        if(sz[x]>sz[y])swap(x,y);
        fa[x]=y;sz[y]+=sz[x];
        p[++tot]=x;q[tot]=y;
    }
    void back(int mu)
    {
        while(tot>mu){
            int x=p[tot],y=q[tot];
            fa[x]=x;sz[y]-=sz[x];
            if(sz[x]%2==1 && sz[y]%2==1)odd+=2;
            tot--;
        }
    }
    
}D;
struct node
{
    int x,y,z;
}a[maxn];
int p[maxn],rnk[maxn];
bool cmp(int x,int y)
{
    return a[x].z<a[y].z;
}
void solve(int l,int r,int ll,int rr){
    if(l>r)return;
    int tmp=D.tot;
    //cout<<l<<" "<<r<<" "<<a[p[ll]].z<<" "<<a[p[rr]].z<<" "<<D.tot<<endl;
    int mid=(l+r)/2;
    for(int i=l;i<=mid;i++)if(rnk[i]<ll)D.merge(a[i].x,a[i].y);
    int nmid=-1;
    for(int i=ll;i<=rr && D.odd;i++){
        if(p[i]<=mid){
            D.merge(a[p[i]].x,a[p[i]].y);
        }
        if(D.odd==0){
            nmid=i;
            break;
        }
    }
    D.back(tmp);
    if(nmid==-1){
        for(int i=l;i<=mid;i++)ans[i]=-1;
        for(int i=l;i<=mid;i++)if(rnk[i]<ll)D.merge(a[i].x,a[i].y);
        solve(mid+1,r,ll,rr);
        D.back(tmp);
        return;
    }
    ans[mid]=a[p[nmid]].z;
    for(int i=ll;i<nmid;i++){
        if(p[i]<l)D.merge(a[p[i]].x,a[p[i]].y);
    }
    solve(l,mid-1,nmid,rr);
    D.back(tmp);
    for(int i=l;i<=mid;i++)if(rnk[i]<ll)D.merge(a[i].x,a[i].y);
    solve(mid+1,r,ll,nmid);
    D.back(tmp);
}
int main()
{
//    freopen("1.in","r",stdin);
//    freopen("1.out","w",stdout);
    scanf("%d%d",&n,&m);
    D.odd=n;
    for(int i=1;i<=n;i++)D.fa[i]=i,D.sz[i]=1;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
        p[i]=i;
    }
    sort(p+1,p+m+1,cmp);
    for(int i=1;i<=m;i++)rnk[p[i]]=i;
    solve(1,m,1,m);
    for(int i=1;i<=m;i++)
    {
        printf("%d\n",ans[i]);
    }
    return 0;
}
/*
4 10
2 1 999999029
3 1 999999800
4 2 999999466
1 3 9961
1 3 9960
2 4 9943
3 1 9938
1 3 9923
4 2 9917
3 4 9903

3 4 9890
4 1 9884
1 3 9880
2 1 9862
1 2 9856
*/
CDQ分治

 

3.线段树做法

通过上一个方法,就可以容易发现,如果一条边在第i时间在答案中,那么它从出生开始到i都在答案中,于是用线段树维护每个时间有哪些边在,然后更新答案,线段树用来存储这一段区间里必有哪些边,依然需要可持久化堆。

//start:   finish:
#include<bits/stdc++.h>
#define maxn 1600005
using namespace std;
int n,m;
int ans[maxn];
struct node{
    int x,y,z;
}a[maxn];
int p[maxn],now;
vector <int> v[maxn];
bool cmp(int x,int y)
{
    return a[x].z<a[y].z;
}
struct DUI
{
    int last,tot;
    int fa[maxn],sz[maxn];
    int p[maxn],q[maxn];
    int odd;
    int find(int x){
        if(fa[x]==x)return x;
        return find(fa[x]);
    }
    void bei(){
        last=tot;
    }
    void merge(int x,int y){
        if(find(x)==find(y))return;
        x=find(x);y=find(y);
        if(sz[x]%2==1 && sz[y]%2==1)odd-=2;
        if(sz[x]>sz[y])swap(x,y);
        fa[x]=y;sz[y]+=sz[x];
        p[++tot]=x;q[tot]=y;
    }
    void back(int mu)
    {
        while(tot>mu){
            int x=p[tot],y=q[tot];
            fa[x]=x;sz[y]-=sz[x];
            if(sz[x]%2==1 && sz[y]%2==1)odd+=2;
            tot--;
        }
    }
    
}D;
void modify(int x,int l,int r,int tx,int ty,int wh){
    if(l>=tx && r<=ty){
        v[x].push_back(wh);
        return;
    }
    int mid=(l+r)/2;
    if(mid>=tx)modify(x*2,l,mid,tx,ty,wh);
    if(mid<ty)modify(x*2+1,mid+1,r,tx,ty,wh);
}
void solve(int x,int l,int r){
    int tmp=D.tot;
//    cout<<x<<" "<<l<<" "<<r<<" "<<D.odd<<" "<<v[x].size()<<endl;
    for(int i=0;i<(int)v[x].size();i++)D.merge(a[v[x][i]].x,a[v[x][i]].y);
    if(l!=r){
        int mid=(l+r)/2;
        solve(x*2+1,mid+1,r);
        solve(x*2,l,mid);
    }
    else{
    //    cout<<D.odd<<endl;
        for(;now<=m && D.odd;now++){
            int id=p[now];
            if(id>l)continue;
            D.merge(a[id].x,a[id].y);
            if(id<l){
            //    cout<<id<<" "<<l-1<<" "<<id<<endl;
                modify(1,1,m,id,l-1,id);
            }
        }
        if(D.odd)ans[l]=-1;else ans[l]=a[p[now-1]].z;
    }
    D.back(tmp);
}
int main(){
    scanf("%d%d",&n,&m);
    D.odd=n;
    for(int i=1;i<=n;i++){
        D.sz[i]=1;
        D.fa[i]=i;
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
        p[i]=i;
    }
    sort(p+1,p+m+1,cmp);
    now=1;
    solve(1,1,m);
    for(int i=1;i<=m;i++){
        printf("%d\n",ans[i]);
    }
    return 0;
}
Segment Tree

 

小结:

这三个方法都建立在前面的结论上,这个结论还是很强大的

方法一:

方法二:

方法三:

后面两种方法的代码长度和时间都比第一种短,但第一种最简单直观。

个人还是非常喜欢这道题的。

转载于:https://www.cnblogs.com/sillygirl/p/6323306.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值