1. 程式人生 > 實用技巧 >[CF981E] Addition on Segments - 線段樹分治,bitset

[CF981E] Addition on Segments - 線段樹分治,bitset

Description

給定長度為 \(n\) 的序列,有 \(q\) 條操作,每條操作為將區間 \([l,r]\) 加上 \(x\)。序列初始都為 \(0\)。問有多少個 $ k \in [1,n]$ 滿足能從 \(q\) 條操作中選出若干條操作後使得序列的最大值為 \(k\)

Solution

如果能有方案使得某個位置的值變成 \(k\),就一定存在方案使得最大值變為 \(k\)

考慮一個暴力,對於每個位置分別考慮是否能使它變為 \(k\),設 \(f[i][j]\) 表示前 \(i\) 條操作中選取若干操作是否能使得該位置的和變為 \(j\),用 Bitset 優化,總時間複雜度 \(O(\frac 1 w n^2 q)\)

考慮線段樹分治,將 \(q\) 條操作中的每個區間拆分掛在線段樹的若干結點上,然後將線段樹 DFS 一遍,過程中利用結點上掛的區間來修改,歷史記錄用棧儲存,這樣回溯時可以撤銷,到達葉子結點時即可得到當結點的答案。每次修改的複雜度為 \(O(\frac 1 w n)\),最多有 \(O(q \log n)\) 個拆分後的區間,故總時間複雜度為 \(O(\frac 1 w nq \log n)\)

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

#define int long long
#define bst bitset<10005>
const int N = 1000005;

int n,q,l[N],r[N],x[N];
vector <int> tg[N]; // Tags on segment tree nodes
stack <bst> sta;
bst ans;

void modify(int p,int l,int r,int ql,int qr,int key)
{
    if(l>qr || r<ql) return;
    if(l>=ql&&r<=qr)
    {
        tg[p].push_back(key);
    }
    else
    {
        modify(p*2,l,(l+r)/2,ql,qr,key);
        modify(p*2+1,(l+r)/2+1,r,ql,qr,key);
    }
}

void solve(int p,int l,int r)
{
    bst now=sta.top();
    for(int i:tg[p])
    {
        now|=now<<i;
    }
    if(l==r)
    {
        ans|=now;
    }
    else
    {
        sta.push(now);
        solve(p*2,l,(l+r)/2);
        solve(p*2+1,(l+r)/2+1,r);
        sta.pop();
    }
}

signed main()
{
    ios::sync_with_stdio(false);

    cin>>n>>q;
    for(int i=1;i<=q;i++)
    {
        cin>>l[i]>>r[i]>>x[i];
        modify(1,1,n,l[i],r[i],x[i]);
    }

    bst b0;
    b0[0]=1;
    sta.push(b0);

    solve(1,1,n);

    int cnt=0;
    for(int i=1;i<=n;i++)
    {
        if(ans[i]) ++cnt;
    }
    cout<<cnt<<endl;
    for(int i=1;i<=n;i++)
    {
        if(ans[i]) cout<<i<<" ";
    }
}