【学习笔记】CF603E Pastoral Oddities

先不考虑数据结构部分,尝试猜一下结论。

结论:一个连通块有解当且仅当连通块的度数为偶数。

然后这题要你最大边权最小。最无脑的方法就是直接上 lct \text{lct} lct

我第一眼想到的还是整体二分。这玩意非常好写。

但是为什么也可以用线段树分治来做呢。这需要简单的分析一下性质。考虑求出每条边在哪些生成树上出现过,分析可知这一定是一段区间。

然后是非常玄学的操作。考虑离线,然后 倒着处理询问 ,这样的话一条边如果原来在生成树上,那么删去一条边后显然还是在生成树上,这意味着恰好有一条边被加了进去,而这条边被加进去的条件就是连接的两个点不联通。

基于上述分析,我们可以尝试分治。首先,计算出影响区间的右端点在 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]之间的边,那么我们就要先算出哪些边的影响区间 ≥ m i d + 1 \ge mid+1 mid+1,哪些边的影响区间 ≤ m i d \le mid mid,这样就将边集分成了两部分。使用可撤销并查集,然后往两边递归即可。怎么计算答案呢,发现递归到叶子节点的时候可以把那颗生成树算出来,然后就做完了。

事实上算边的影响区间部分我们可以不用真的将边分到两个集合中去。考虑更聪明的想法,倒着往前处理询问的时候,之前在生成树上的边还是在生成树上,那么我们事实上只需要在此基础上加入边权更大的边(当然这条边必须合法),直到得到的图满足条件。那么我们就知道新加入的这些边的影响区间的右端点就是当前这个位置,在线段树上对应部分打一个标记即可。这个半在线做法非常神奇。

复杂度 O ( n log ⁡ n log ⁡ m ) O(n\log n\log m) O(nlognlogm)

实现了后一种较为复杂的方法。

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
#define inf 0x3f3f3f3f
#define db double
#define cpx complex<db>
using namespace std;
const int N=3e5+5;
int n,m,cnt,res[N],now;
int fa[N],s[N],sa[N];
vector<int>G[N<<2];
struct node{
    int x,y,z;
}e[N];
bool cmp(int x,int y){
    return e[x].z<e[y].z;
}
int find(int x){
    return fa[x]==x?x:find(fa[x]);
}
void modify(int p,int l,int r,int ql,int qr,int x){
    if(ql>qr)return;
    if(ql<=l&&r<=qr){
        G[p].pb(x);
        return;
    }
    int mid=l+r>>1;
    if(ql<=mid)modify(p<<1,l,mid,ql,qr,x);
    if(mid<qr)modify(p<<1|1,mid+1,r,ql,qr,x);
}
void solve(int p,int l,int r){
    vector<pair<int,int>>bak;
    for(auto pos:G[p]){
        int x=find(e[pos].x),y=find(e[pos].y);
        if(x!=y){
            cnt-=(s[x]&1)+(s[y]&1);
            if(s[x]>s[y])swap(x,y);
            bak.pb(make_pair(x,y));
            fa[x]=y,s[y]+=s[x],cnt+=(s[y]&1);
        }
    }
    if(l==r){
        while(cnt&&now<=m){
            int x=find(e[sa[now]].x),y=find(e[sa[now]].y);
            if(sa[now]<l)modify(1,1,m,sa[now],l-1,sa[now]);//fixed
            if(sa[now]<=l&&x!=y){
                cnt-=(s[x]&1)+(s[y]&1);
                if(s[x]>s[y])swap(x,y);
                bak.pb(make_pair(x,y));
                fa[x]=y,s[y]+=s[x],cnt+=(s[y]&1);
            }
            now++;
        }
        if(!cnt)res[l]=e[sa[now-1]].z;
        else res[l]=-1;
    }
    else{
        int mid=l+r>>1;
        solve(p<<1|1,mid+1,r);
        solve(p<<1,l,mid);
    }
    reverse(bak.begin(),bak.end());
    for(auto x:bak){
        cnt-=s[x.se]&1;
        fa[x.fi]=x.fi,s[x.se]-=s[x.fi];
        cnt+=(s[x.fi]&1)+(s[x.se]&1);
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;for(int i=1;i<=n;i++)fa[i]=i,s[i]=1;
    for(int i=1;i<=m;i++){
        cin>>e[i].x>>e[i].y>>e[i].z,sa[i]=i;
    }
    sort(sa+1,sa+1+m,cmp);
    cnt=n,now=1;
    solve(1,1,m);
    for(int i=1;i<=m;i++)cout<<res[i]<<"\n";
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值