互达的集合(线段树)

problem

给定数组 l , r l,r l,r。求有多少个非空集合 S S S,满足 ∀ i , j ∈ S   l i ≤ j ≤ r i \forall_{i,j\in S}\ l_i\le j\le r_i i,jS lijri

集合内对于任意一个点而言,其余点均能被自己的范围覆盖到。

n ≤ 2 e 5 n\le 2e5 n2e5

solution

分享一下考场心路历程:考试是分了五个部分分的。

第一个部分分直接 2 n 2^n 2n 暴枚,很快拿下。

第二个部分分 n ≤ 2000 n\le 2000 n2000,一看就是 n 2 n^2 n2 算法,我就想到了固定集合选取点的最左最右点,然后看有多少个点满足 l i ≤ L ∧ R ≤ r i ∧ L ≤ i ≤ R l_i\le L\wedge R\le r_i\wedge L\le i\le R liLRriLiR,天哪太多的偏序关系了,我直接抡 KD-tree \text{KD-tree} KD-tree,好家伙大样例虽然对了却要跑 6 s 6s 6s,试问谁遭得住?

第三个部分分 r i − l i ≤ 10 r_i-l_i\le 10 rili10。我就只枚举最左点,然后暴搜/ Hash \text{Hash} Hash 集合个数,不超过 2 10 2^{10} 210,算出来大约 2 e 8 2e8 2e8 但肯定跑不满。一看样例全过。(测评最后结果 wa \text{wa} wa 了,但这不重要了)

第四个部分分, 5 e 4 5e4 5e4 可能是分块吧。对我没有太多帮助。

第五个部分分,就是最大的范围了。

由第二个部分的枚举 l , r l,r l,r 我突然想到了前不久狂做 LCT \text{LCT} LCT 的一类连通性题。全都是枚举了右端点然后用线段树上的节点做左端点,并用线段树维护答案个数。

这里我想类比做法:蒋所有 [ l i , r i ] [l_i,r_i] [li,ri] 全挂到 r i r_i ri 点下。

枚举 i i i 做右端点,然后线段树上的节点做左端点。

新右端点会对 [ l i , i ] [l_i,i] [li,i] 都提供 1 1 1 的个数(答案是 2 cnt 2^\text{cnt} 2cnt,每个数选或不选)

i i i 右移一位的时候,就要去掉所有 r j = i r_j=i rj=i j j j 曾经的贡献。

然后,然后,我发现强制左右端点后虽然不会算重,但是应该算的是 2 c n t − 2 2^{cnt-2} 2cnt2(去掉左右端点选和不选的考虑)

结果这样又会影响我线段树的标记下放,那我的线段树不是废了?!!

最后不管是在线段树上维护个数还是直接维护贡献都无法正确避免 − 2 -2 2 带来的影响,在主函数内我也无法去掉。我就直接开始摆烂了。。。

结果下午过来,给小同志讲了一遍后没讲懂,被她质疑的我就仔细想了一下,突然想到了貌似大概也许可以这么来,重构一遍直接过!心中草泥马奔腾。。。

从左到右枚举集合的右端点 r r r(最大的点)。

然后线段树上的节点 l l l 强制是集合的左端点,且维护的信息是 l , r l,r l,r 做左右端点时的答案。

考虑统计右端点为 i i i 的答案。

首先要激活线段树上的 i i i 节点,初始化为 1 1 1,代表 [ i , i ] [i,i] [i,i] 集合。

然后统计线段树上 [ l i , i ] [l_i,i] [li,i] 区间的答案。

接下来 i i i 要右移 + 1 +1 +1,我们需要重新修改维护线段树上的信息。

  • 对区间 [ l i , i − 1 ] [l_i,i-1] [li,i1] 进行区间 × 2 \times 2 ×2

    表示当这些区间中的节点为集合左端点时, i i i 是合法互达备选点,存在选和不选的方案考虑。

    但不能对 i i i 也进行 × 2 \times 2 ×2,理由在心路历程后面提到,它是被强制了的。

  • 对所有 r j = i r_j=i rj=i j j j,从 i + 1 i+1 i+1 开始当右端点后, j j j 就不可能再到达集合的右端点了,再也不能成为合法互达备选点,所以要把 j j j 之前提供的贡献全都去掉。

    要进行 [ l j , j − 1 ] [l_j,j-1] [lj,j1] 区间 / 2 /2 /2,以及节点 j j j 的直接赋 0 0 0。(这样 j j j 就在也不可能成为集合左端点被纳入贡献了)

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

code

#include <bits/stdc++.h>
using namespace std;
#define maxn 200005
#define int long long
#define mod 998244353

namespace SGT {
    struct node { int cnt, tag; }t[maxn << 2];
    #define lson now << 1
    #define rson now << 1 | 1
    #define mid  (l + r >> 1)
    void build( int now, int l, int r ) {
        t[now].tag = 1;
        if( l == r ) return;
        build( lson, l, mid );
        build( rson, mid + 1, r );
    }
    void pushdown( int now ) {
        if( t[now].tag == 1 ) return;
        ( t[lson].tag *= t[now].tag ) %= mod;
        ( t[rson].tag *= t[now].tag ) %= mod;
        ( t[lson].cnt *= t[now].tag ) %= mod;
        ( t[rson].cnt *= t[now].tag ) %= mod;
        t[now].tag = 1;
    }
    void modify( int now, int l, int r, int p, int x ) {
        if( l == r ) { t[now].cnt = x ? 1 : 0; return; }
        pushdown( now );
        if( p <= mid ) modify( lson, l, mid, p, x );
        else modify( rson, mid + 1, r, p, x );
        t[now].cnt = ( t[lson].cnt + t[rson].cnt ) % mod;
    }
    void modify( int now, int l, int r, int L, int R, int x ) {
        if( R < l or r < L ) return;
        if( L <= l and r <= R ) { 
            ( t[now].cnt *= x ) %= mod;
            ( t[now].tag *= x ) %= mod;
            return;
        }
        pushdown( now );
        modify( lson, l, mid, L, R, x );
        modify( rson, mid + 1, r, L, R, x );
        t[now].cnt = ( t[lson].cnt + t[rson].cnt ) % mod;
    }
    int query( int now, int l, int r, int L, int R ) {
        if( R < l or r < L ) return 0;
        if( L <= l and r <= R ) return t[now].cnt;
        pushdown( now );
        return query( lson, l, mid, L, R ) + query( rson, mid + 1, r, L, R );
    }
}

int n;
int l[maxn], r[maxn];
vector < int > G[maxn];
signed main() {
    scanf( "%lld", &n );
    for( int i = 1;i <= n;i ++ ) scanf( "%lld", &l[i] );
    for( int i = 1;i <= n;i ++ ) scanf( "%lld", &r[i] );
    for( int i = 1;i <= n;i ++ ) G[r[i]].push_back( i );
    SGT :: build( 1, 1, n ); //线段树上强制节点为选取集合最小值
    int ans = 0;
    int inv = mod + 1 >> 1;
    for( int i = 1;i <= n;i ++ ) { //强制选取的集合最大值
        SGT :: modify( 1, 1, n, i, 1 ); //把i激活 初始化1
        ( ans += SGT :: query( 1, 1, n, l[i], i ) ) %= mod;
        SGT :: modify( 1, 1, n, l[i], i - 1, 2 ); //对于最小值属于[l[i],i-1]的点 会有一个新的i可以互达
        for( int j : G[i] ) {
            SGT :: modify( 1, 1, n, l[j], j - 1, inv );
            SGT :: modify( 1, 1, n, j, 0 );
        }
    }
    printf( "%lld\n", ans );
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值