Loading

题解-CF1389F Bicolored Segments

题面

CF1389F Bicolored Segments

\(n\) 条线段 \([l_i,r_i]\),每条有个颜色 \(t_i\in\{0,1\}\),求最多选出多少条线段,使没有不同颜色的线段相交。

数据范围:\(1\le n\le 2\cdot 10^5\)\(1\le l_i\le r_i\le 10^9\)


蒟蒻语

昨天蒟蒻打 CF,发挥得不错,迷惑回橙。但是蒟蒻没做出这题,赛后想了好久感觉这题很奇妙,于是蒻蒻地来写篇题解。


蒟蒻解一

线段树维护 dp。

先将每条线段 \(l_i,r_i\) 离散化,坐标范围为 \([0,cnt]\)

\(f(i,j,k)\) 表示看了 \([0,i]\)\([j+1,i]\) 的线段颜色都为 \(k\) 的最多线段数。

\[j<i:f(i,j,k)=f(i-1,j,k)+\sum_{x=1}^{n}[l_x>j][r_x=i] \]

\[f(i,i,k)=\max[\max_{j=0}^{i-1}f(i,j,!k),\max_{j=0}^{i-1}f(i,j,k)] \]

那么答案是 \(\max_{j=0}^{cnt}f(cnt,j,0/1)\)

\(ca_i\) 这个 vector 存放 \(r_x=i\)\(x\)

所以可以用一个线段树代替 \(j\) 维,把 \(i\) 维滚掉,实现上述dp。

时间复杂度 \(\Theta(n\log n)\)

代码

#include <bits/stdc++.h>
using namespace std;
 
//Start
typedef long long ll;
typedef double db;
#define mp(a,b) make_pair(a,b)
#define x first
#define y second
#define be(a) a.begin()
#define en(a) a.end()
#define sz(a) int((a).size())
#define pb(a) push_back(a)
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
 
//Data
const int N=2e5,M=(N<<1)+1;
int n,l[N],r[N],t[N],cnt,b[M],ans;
vector<int> ca[M];
 
//Segmenttree
const int T=M<<2;
#define lk k<<1
#define rk k<<1|1
struct Segmenttree{ //线段树,下标为坐标,维护区间加、全局最大值
    int mx[T],mk[T];
    void pushup(int k){mx[k]=max(mx[lk],mx[rk]);}
    void pm(int k,int v){mk[k]+=v,mx[k]+=v;}
    void pushdown(int k){if(mk[k]) pm(lk,mk[k]),pm(rk,mk[k]),mk[k]=0;}
    void fix(int x,int y,int v,int k,int l,int r){
        if(x<=l&&r<=y) return pm(k,v);
        pushdown(k);
        int mid=(l+r)>>1;
        if(mid>=x) fix(x,y,v,lk,l,mid);
        if(mid<y) fix(x,y,v,rk,mid+1,r);
        pushup(k);
    }
    int Mx(){return mx[1];}
    void Print(int k,int l,int r){
        if(l==r){cout<<mx[k]<<' ';return;}
        pushdown(k);
        int mid=(l+r)>>1;
        Print(lk,l,mid),Print(rk,mid+1,r);
    }
}g[2];
 
//Main
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>l[i]>>r[i]>>t[i],--t[i];
        b[cnt++]=l[i],b[cnt++]=r[i];
    }
    b[cnt++]=0,sort(b,b+cnt),cnt=unique(b,b+cnt)-b;
    for(int i=0;i<n;i++){
        l[i]=lower_bound(b,b+cnt,l[i])-b;
        r[i]=lower_bound(b,b+cnt,r[i])-b;
        ca[r[i]].pb(i);
    }
    for(int i=1;i<cnt;i++){
        for(int x:ca[i]) g[t[x]].fix(0,l[x]-1,1,1,0,cnt);
        g[0].fix(i,i,g[1].Mx(),1,0,cnt),g[1].fix(i,i,g[0].Mx(),1,0,cnt);//这么写也是可以的
    }
    cout<<max(g[0].Mx(),g[1].Mx())<<'\n';
    return 0;
}

蒟蒻解二

萌新初学 OI 的时候,有一个贪心问题:求最多线段互不相交。做法是右端点再左端点双关键字排序,然后贪心取舍一下。

这题可以同样地骚操作:

初始化答案为 \(n\)。用两个 multiset 记录两种颜色分别选了哪些线段。

顺序枚举排序了的线段,如果没有选了的线段与当前线段异色并重合,那么蒟蒻们可以很开心地选上这条线段。

否则把右端点在当前线段左端点右边并且最近的异色线段从 multiset 中删除,不往 multiset 中加入当前线段,把答案 \(-1\),表示一个对抗抵消的过程。

比如加了一条 \(0\) 线段,然后再加一条 \(1\) 线段与它抵消。这时如果来 \(2\)\(1\) 线段,相当于选了 \(3\)\(1\) 线段;如果来 \(2\)\(0\) 线段,相当于选了 \(3\)\(0\) 线段。

这种思想类似求序列众数时的对抗抵消选举和模拟网络流反悔推流。

时间复杂度 \(\Theta(n\log n)\)

代码

#include <bits/stdc++.h>
using namespace std;

//Start
typedef long long ll;
typedef double db;
#define mp(a,b) make_pair(a,b)
#define x first
#define y second
#define be(a) a.begin()
#define en(a) a.end()
#define sz(a) int((a).size())
#define pb(a) push_back(a)
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;

//Data
const int N=2e5;
int n,ans;
struct S{int l,r,t;}a[N];
multiset<int> g[2];

//Main
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n,ans=n;
    for(int i=0;i<n;i++)
        cin>>a[i].l>>a[i].r>>a[i].t,--a[i].t;
    sort(a,a+n,[&](const S p,const S q){return p.r==q.r?p.l<q.l:p.r<q.r;});
    for(int i=0;i<n;i++)
        if(g[!a[i].t].lower_bound(a[i].l)==en(g[!a[i].t])) g[a[i].t].insert(a[i].r);
        else ans--,g[!a[i].t].erase(g[!a[i].t].lower_bound(a[i].l));
    cout<<ans<<'\n';
    return 0;
}

祝大家学习愉快!

posted @ 2020-07-30 19:56  George1123  阅读(217)  评论(0编辑  收藏  举报