单调栈 or 线段树扫描线 ---- E. Delete a Segment [单调栈+二分] [扫描线处理空白位置的技巧乘2]

题目链接


题目大意:

给出 n n n个线段代表集合,现在问若可以将其中任意一个线段删除,则能够形成最多多少个独立的集合(取并集后)


解题思路1:

  1. 首先我们先对线段按照起点排序
  2. 那么我们枚举删除的线段 i i i,那么整个区域就切成了 [ 1 , i − 1 ] [1,i-1] [1,i1] [ i + 1 , n ] [i+1,n] [i+1,n]的两段
    在这里插入图片描述
  3. 现在我们就是要把 [ 1 , i − 1 ] [1,i-1] [1,i1] [ i + 1 , n ] [i+1,n] [i+1,n]里面的线段合并就可以了
  4. 我们要把 [ 1 , i − 1 ] [1,i-1] [1,i1]里面的线段算出来,并且统计出 [ 1 , i − 1 ] [1,i-1] [1,i1]里面最远的 r r r是多大 m a x r i maxr_i maxri
  5. 然后我们逆着枚举线段,用单调栈去维护 [ i + 1 , n ] [i+1,n] [i+1,n]里面线段(合并之后)的左端点,然后用 m a x r i maxr_i maxri去查询里面第一个大于 m a x r i maxr_i maxri的位置就是后面的答案,就是去除重叠的部分
  6. 对于前面我们也可以先预处理出来每个位置线段合并的情况

Ac code 1

#include <bits/stdc++.h>
#define mid ((l + r + 1) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define LLF 0x3f3f3f3f3f3f3f3f
#define f first
#define s second
#define endl '\n'
using namespace std;
const int N = 2e6 + 10, mod = 1e9 + 9;
const int maxn = 500010;
const long double eps = 1e-5;
const int EPS = 500 * 500;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
typedef pair<double,double> PDD;
template<typename T> void read(T &x) {
   x = 0;char ch = getchar();ll f = 1;
   while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
   while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args) {
   read(first);
   read(args...);
}
PII seg[maxn];
int maxr[maxn], res[maxn];
stack<int> stk;
vector<int> cur;
int main() {
//    IOS;
    int T;
    cin >> T;
    while(T--) {
        int n;
        cin >> n;
        for(int i = 1; i <= n; ++ i) cin >> seg[i].first >> seg[i].second;
        sort(seg+1,seg+1+n);
        ms(maxr,-INF);// 注意这里初始化为负数
        for(int i = 2; i <= n; ++ i) maxr[i] = max(maxr[i-1],seg[i-1].second);
        for(int i = 1; i <= n; ++ i) {
           if(i==1) stk.push(seg[i].second);
           else {
              if(stk.top() >= seg[i].first) {
                  if(stk.top() < seg[i].second) {
                      stk.pop();
                      stk.push(seg[i].second);
                  }
              } else stk.push(seg[i].second);
           }
           res[i] = stk.size();
        }
        
        cur.push_back(INF);
        while(!stk.empty()) stk.pop();
        int ans = 0;
        for(int i = n; i >= 1; -- i) {
            int l = 0, r = cur.size()-1;
			while(l<r) {
				if(cur[mid]<=maxr[i]) r = mid-1;
				else l = mid;
			} 
            ans = max(ans,res[i-1]+l);// 前半段+后半段答案
            while(!cur.empty() && cur.back() <= seg[i].second) cur.pop_back();
            cur.push_back(seg[i].first);
        }
        cur.clear();
        cout << ans << "\n";
    }
    return 0;
}

解题思路2:

  1. 首先对于 [ a , b ] [a,b] [a,b] [ b + 1 , c ] [b+1,c] [b+1,c]对于这两个线段正常的扫描线对于这两种看起来是连在一起的,我们可以把区间乘以2变成 [ 2 a , 2 b ] [2a,2b] [2a,2b] [ 2 b + 2 , 2 c ] [2b+2,2c] [2b+2,2c],那么中间就会有空白啦 [ 2 b + 1 , 2 b + 1 ] [2b+1,2b+1] [2b+1,2b+1]
  2. 那么我们就把区间乘2,然后把空白区间一起放进去离散化
  3. 因为有的线段可以是一个点,那么我们要把扫描线线段树的叶子节点变成一个点
  4. 我们还要维护两个 l c o v e r lcover lcover r c o v e r rcover rcover,判断这个区间的左右是否被覆盖了 就这个区间合并的时候要用
  5. 剩下的就和扫描线差不多了

AC code2

#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define LLF 0x3f3f3f3f3f3f3f3f
#define f first
#define s second
#define endl '\n'
using namespace std;
const int N = 2e6 + 10, mod = 1e9 + 9;
const int maxn = N;
const long double eps = 1e-5;
const int EPS = 500 * 500;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
typedef pair<double,double> PDD;
template<typename T> void read(T &x) {
   x = 0;char ch = getchar();ll f = 1;
   while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
   while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args) {
   read(first);
   read(args...);
}
struct Segtree {
    int tr[maxn<<2];
    bool led[maxn<<2];
    bool red[maxn<<2];
    int cover[maxn<<2];
    void build(int rt, int l, int r) {
        tr[rt]=led[rt]=red[rt]=cover[rt]=0;
        if(l==r) return;
        build(rt<<1,l,mid);
        build(rt<<1|1,mid+1,r);
    }
    void pushup(int rt) {
        if(cover[rt]) tr[rt]=led[rt]=red[rt]=1;
        else {
           tr[rt]=tr[rt<<1]+tr[rt<<1|1];
           led[rt] = led[rt<<1];
           red[rt] = red[rt<<1|1];
           if(red[rt<<1]&&led[rt<<1|1]) tr[rt]--;
           // 如果中间连起来了线段连起来了,线段个数减1 
        }
    }
    void update(int rt, int l, int r, int posl, int posr, int val) {
        if(posl<=l && posr>=r) {
            cover[rt] += val;
            pushup(rt);// 扫描线特性
            return;
        }
        if(posl<=mid) update(rt<<1,l,mid,posl,posr,val);
        if(posr>mid) update(rt<<1|1,mid+1,r,posl,posr,val);
        pushup(rt);
    }
}sgt;

vector<int> lis;
PII seg[maxn];
int n;
inline int getid(int x) {
    return lower_bound(lis.begin(),lis.end(),x) - lis.begin() + 1;
}
inline void init() {
    cin >> n;
    for(int i = 1; i <= n; ++ i) {
        int u, v;
        cin >> u >> v;
        seg[i] = {2*u,2*v};// 离散化
        lis.push_back(2*u);
        lis.push_back(2*u-1);
        lis.push_back(2*v);
        lis.push_back(2*v+1);
    }
    sort(lis.begin(),lis.end());
    lis.erase(unique(lis.begin(),lis.end()),lis.end());
    for(int i = 1; i <= n; ++ i) {
        seg[i].first = getid(seg[i].first);
        seg[i].second = getid(seg[i].second);
    }
}
 
int main() {
    IOS;
    int _;
    cin >> _;
    while(_--) {
        init();
        sgt.build(1,1,lis.size());
        for(int i = 1; i <= n; ++ i) 
            sgt.update(1,1,lis.size(),seg[i].first,seg[i].second,1);
        int ans = 0;
        for(int i = 1; i <= n; ++ i) {
            sgt.update(1,1,lis.size(),seg[i].first,seg[i].second,-1);
            ans = max(ans,sgt.tr[1]);
            sgt.update(1,1,lis.size(),seg[i].first,seg[i].second,1);     
        }
        cout << ans << "\n";
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值