序列消除(erase)

题面

【题目描述】

按如下方式定义一个非空序列 \(b_1,b_2,\ldots,b_m\) 的消除方案:

维护一个由非空区间组成的集合 \(S\)。初始时 \(S=\{[1,m]\}\)。

区间按照二元组 \((l,r)\) 的字典序比较大小。不断进行下列操作,直到 \(S\) 为空:

  1. 取出 \(S\) 中最小的区间 \([l,r]\),并将其从 \(S\) 中删除;
  2. 选择一个整数 \(k\),要求在 \(b_l,b_{l+1},\ldots,b_r\) 中至少出现一次 \(k\);
  3. 将 \([l,r]\) 中所有满足下列条件的非空连续子区间 \([l',r']\) 加入 \(S\):
    • 对任意 \(l'\le i\le r'\),均有 \(b_i\ne k\);
    • 无法在仍然位于 \([l,r]\) 内的前提下,将 \([l',r']\) 向左或向右扩展并继续满足上一条件。

换言之,在区间 \([l,r]\) 中删除所有值等于 \(k\) 的位置后,将剩余的每个极长连续段加入 \(S\)。

一次消除方案可以用每次操作所选择的 \(k\) 依次组成的序列表示。两个消除方案不同,当且仅当它们对应的选择序列不同。

现在给定一个长度为 \(n\) 的序列 \(a_1,a_2,\ldots,a_n\) 以及 \(q\) 次询问。每次询问给出一个区间 \([l,r]\),你需要求序列 \(a_l,a_{l+1},\ldots,a_r\) 有多少种不同的消除方案。答案可能很大,请对 \(998244353\) 取模。

【样例1输入】

0 2
5 6
3 1 2 3 1
1 1
1 2
1 3
1 4
1 5
2 4
4 3
2 2 2 2
1 4
2 3
4 4

【样例1输出】

1
2
5
6
8
5
1
1
1

【样例1解释】

对于第一组测试数据的第二次询问,子序列为 \((1,2)\)。第一次操作可以选择 \(1\) 或 \(2\),因此共有 \(2\) 种消除方案。

对于只包含同一种数值的非空序列,第一次操作只能选择该数值,随后集合 \(S\) 立即变为空集,因此消除方案数为 \(1\)。

【数据范围与约定】

对于所有测试数据,保证:

  • \(1\le T\le 5\)
  • \(1\le n,q\le 5\times 10^5\)
  • \(1\le a_i\le 20\)
  • \(1\le l\le r\le n\)
  • 单个输入文件中所有测试数据的 \(n+q\) 之和不超过 \(10^6\)

思路

注意,值域 \(V \le 20\)。

朴素暴力

考虑 \(O(n^2 V^2)\) 朴素暴力,设 \(dp_{c,L,R}\) 表示在区间 \([L,R]\) 中第一步选 \(k=c\) 的方案数,设 \(lst_c\) 为顺序向右搜索时最近的满足 \(a_{lst_c}=c\)。如果在 \([L,R]\) 内没有 \(c\) 则 \(dp_{c,L,R}=0\)。

容易得到转移是:如果全部一样,答案为 \(1\);如果不一样则:

\[dp_{c,L,R}=dp_{c,L,lst_c} \sum _{x \ne c} dp_{x,lst_c+1,R} \]

优化

考虑按右端点 \(r\) 扫描线,然后去更新每个 \(dp_c\)。既是我们在到 \(r\) 的时候我们知道了 \(\forall{L},dp_{c,L,r-1}\)。所以可以设新的 \(dp_{c,L}\) 即可。

当 \(a_r \ne c\) 时

\(lst_c\) 不会更新,所以我们发现这相当于把 \(L \from [1,lst_l]\) 的都除掉原来的再乘上新的答案。新的答案就是来自于原式的后半段 \(\sum _{x \ne c} dp_{x,lst_c+1,R}\),所以我们只要整段乘上即可,然后又发现这相当于所有的都乘上(\(0\) 乘完还是 \(0\)),所以用一个标记即可直接维护,新加进去的数直接除以原来的标记即可。

当 \(a_r \ne c\) 时

要加入 \(L \from [lst_c+1,r]\) 的值,注意到这种更新是 \(O(n)\) 次的,所以也可以直接枚举 \(x \ne c\) 暴力更新。

正确代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
#define freop(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout);
#define endl '\n'
#define inf 0x7f7f7f7f
#define lb(x) ((x)&-(x))
#define pii pair<int,int>
#define fir first
#define sec second
#define mkp(a,b) make_pair(a,b)
const int N=5e5+5,M=21,mod=998244353;
int C,T,n,q,a[N],lst[M],l[N],id[M];
ll dp[M][N],mul[M],pre[M],ans[N];
vector<int> qy[N];

inline ll ksm(ll a,ll b=mod-2){
    ll c=1;
    while(b){
        if(b&1) c=c*a%mod;
        b>>=1,a=a*a%mod;
    }
    return c;
}
inline bool cmp(int x,int y){return lst[x]>lst[y];}
inline ll md(ll asd){return asd-(asd>=mod)*mod;}
inline ll ask(int x,int y){return dp[x][y]*mul[x]%mod;}

int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    cout.tie(0);
    freop("erase");
//↑以上为初始化↑
//------------------------------
    cin >> C >> T;
    while(T--){
        cin >> n >> q,memset(dp,0,sizeof(dp));
        for(int i = 1;i<=n;++i)
            cin >> a[i],vector<int>().swap(qy[i]);
        for(int i = 1;i<M;++i) lst[i]=0,dp[i][0]=pre[i]=mul[i]=1;
        for(int i = 1,r;i<=q;++i)
            cin >> l[i] >> r,ans[i]=0,
            qy[r].push_back(i);
        for(int r = 1;r<=n;++r){
            ll div=ksm(mul[a[r]]);
            for(int l = lst[a[r]]+1;l<r;++l){
                ll sum=0;
                for(int i = 1;i<M;++i)
                    sum=md(sum+ask(i,l));
                dp[a[r]][l]=div*sum%mod;
            }
            dp[a[r]][r]=dp[a[r]][r-1],lst[a[r]]=r,pre[a[r]]=mul[a[r]];
            for(int i = 1;i<M;++i) id[i]=i;
            sort(id+1,id+M,cmp);
            for(int ii = 2,i=id[ii];ii<M;i=id[++ii]){
                ll sum=0;
                for(int jj = 1;jj<ii;++jj)
                    sum=md(sum+ask(id[jj],lst[i]+1));
                mul[i]=pre[i]*sum%mod;
            }
            for(int id:qy[r])
                for(int i = 1;i<M;++i)
                    ans[id]=md(ans[id]+ask(i,l[id]));
            // for(int i = 1;i<9;++i){
            //     for(int j = 1;j<=r;++j)
            //         cerr << ask(i,j)%mod << ' ';
            //     cerr << endl;
            // }cerr << endl;
        }
        for(int i = 1;i<=q;++i)
            cout << ans[i] << endl;
    }
    return 0;
}
posted @ 2026-10-21 20:55  elon_giao  阅读(3)  评论(0)    收藏  举报