1 条题解

  • 0
    @ 2026-4-21 20:30:16

    题目重述

    定义 sort(a)\text{sort}(a) 为冒泡排序对数组 aa 排序所需的轮数(即外层循环次数)。
    已知排列 pp 长度为 nn,定义 bi=sort([p1,p2,,pi])b_i = \text{sort}([p_1, p_2, \dots, p_i])

    给定 mm 个约束 (kj,lj,rj)(k_j, l_j, r_j),要求满足:
    xj=#{ybykj,1yn}x_j = \#\{ y \mid b_y \le k_j, 1 \le y \le n\},则 ljxjrjl_j \le x_j \le r_j
    求满足条件的排列个数,模 998244353998244353


    第一步:关键结论——冒泡排序轮数与 LIS 的关系

    对于排列 pp 的前缀 [p1..pi][p_1..p_i],有: [ b_i = i - \text{LIS}([p_1..p_i]) ] 其中 LIS\text{LIS} 是最长递增子序列的长度。

    验证(以 p=[3,1,4,2]p=[3,1,4,2] 为例):

    • i=1i=1[3][3] 的 LIS=1,b1=11=0b_1=1-1=0
    • i=2i=2[3,1][3,1] 的 LIS=1,b2=21=1b_2=2-1=1
    • i=3i=3[3,1,4][3,1,4] 的 LIS=2,b3=32=1b_3=3-2=1
    • i=4i=4[3,1,4,2][3,1,4,2] 的 LIS=2,b4=42=2b_4=4-2=2

    因此: [ b_i \le k \iff i - \text{LIS}_i \le k \iff \text{LIS}_i \ge i - k ]


    第二步:转化约束

    原约束:lj#{ybykj}rjl_j \le \#\{ y \mid b_y \le k_j \} \le r_j

    代入 by=yLISyb_y = y - \text{LIS}_y: [ b_y \le k_j \iff y - \text{LIS}_y \le k_j \iff \text{LIS}_y \ge y - k_j ]

    所以每个约束 (kj,lj,rj)(k_j, l_j, r_j) 等价于:
    在前缀 LIS 序列 LIS1,LIS2,,LISn\text{LIS}_1, \text{LIS}_2, \dots, \text{LIS}_n 中,满足 LISyykj\text{LIS}_y \ge y - k_jyy 的个数在 [lj,rj][l_j, r_j] 之间。


    第三步:LIS 序列的性质

    对于排列 pp,其前缀 LIS 长度序列 ci=LIS([p1..pi])c_i = \text{LIS}([p_1..p_i]) 满足:

    1. c1=1c_1 = 1
    2. ci{ci1,ci1+1}c_{i} \in \{c_{i-1}, c_{i-1}+1\}(每次最多增加 11
    3. 1cii1 \le c_i \le i

    证明:加入新元素 pip_i 后,LIS 要么不变,要么增加 11(如果 pip_i 可以接在某个长度为 ci1c_{i-1} 的递增子序列后面)。


    第四步:转化为序列计数问题

    我们需要统计长度为 nn 的整数序列 c1..cnc_1..c_n 的个数,满足:

    • c1=1c_1 = 1
    • cici1{0,1}c_{i} - c_{i-1} \in \{0, 1\}
    • 1cii1 \le c_i \le i
    • 对于每个约束 jjlj#{ycyykj}rjl_j \le \#\{ y \mid c_y \ge y - k_j \} \le r_j

    并且每个这样的 cc 序列对应多少个排列?
    已知结论:给定 cc 序列,满足它的排列个数为: [ \prod_{i=1}^{n} (i - c_{i-1}) \quad \text{其中} \quad c_0 = 0 ] 因为当 ci=ci1c_i = c_{i-1} 时,pip_i 必须小于所有 LIS 的末尾最小值;当 ci=ci1+1c_i = c_{i-1}+1 时,pip_i 必须大于某个值。

    更准确的公式(来自经典结果): [ \text{count} = \prod_{i=1}^{n} (i - c_{i-1}) ] 其中 c0=0c_0 = 0,且要求 ci1cici1+1c_{i-1} \le c_i \le c_{i-1}+1ciic_i \le i


    第五步:DP 状态设计

    定义 dp[i][j]dp[i][j] 表示前 ii 个位置,ci=jc_i = j 时的总排列数(已乘上系数)。

    转移:

    • 如果 ci=jc_i = j,则 ci1c_{i-1} 可以是 jjj1j-1(如果 j1j \ge 1
    • 乘上系数 ici1i - c_{i-1}(因为新元素有这么多选择)

    即: [ dp[i][j] = dp[i-1][j] \cdot (i - j) + dp[i-1][j-1] \cdot (i - (j-1)) ] 边界:dp[1][1]=1dp[1][1] = 1dp[1][j]=0dp[1][j] = 0 for j1j \ne 1


    第六步:处理约束(标程的核心优化)

    标程中将 nn 个位置划分为若干段,每段内 ii 的取值区间 [l,r][l, r] 使得所有约束的边界条件不变。

    定义 tt 数组为所有出现过的 kjk_j 以及 1-1n1n-1 的排序去重结果。
    对于每个段 [L,R][L, R]L,RL, R 来自 vv 数组,是约束的边界),我们只需要知道:

    • 最小允许的 cic_i 值(由 r1r1 给出)
    • 最大允许的 cic_i 值(由 r2r2 给出)

    其中:

    • r1[i]r1[i] 表示位置 ii 处,cic_i 必须至少为某个值(通过 kjk_j 转换)
    • r2[i]r2[i] 表示位置 ii 处,cic_i 必须不超过某个值

    标程中的 qry1qry2 函数计算的是在区间 [l,r][l, r] 内,cc 值固定时,所有可能的排列数乘积的封闭形式。


    第七步:转移方程的封闭形式

    在一个区间 [L,R][L, R] 内,如果 cic_i 保持为常数 jj,那么: [ \prod_{i=L}^{R} (i - j) = \frac{(R-j)!}{(L-1-j)!} \quad \text{如果 } L > j ] 如果 LjL \le j,则乘积中包含 00,整个为 00

    标程中的 qry1(l, r, k) 计算的是: [ \text{qry1}(l, r, k) = \prod_{i=l}^{r} \max(i - k, 1) \quad \text{的某种变体} ] 实际上它计算的是:

    • 如果 k<lk < l,则返回 (k+1)rl+1(k+1)^{r-l+1}(因为所有 ik1i-k \ge 1
    • 如果 k>rk > r,则返回 (r+1)!l!\frac{(r+1)!}{l!}(因为 iki-k 恒为正且递增)
    • 否则分段计算

    qry2 则是两个 qry1 的差,用于处理 cc 值变化的情况。


    第八步:标程的算法流程

    1. 读入并离散化:将所有的 lj1l_j-1rjr_j 以及 n1n-1 存入 vv,去重排序。将所有 kjk_j 以及 1-1n1n-1 存入 tt,去重排序。
    2. 预处理边界:对每个位置 ii(来自 vv),计算 r1[i]r1[i](最小允许的 cc 的索引)和 r2[i]r2[i](最大允许的 cc 的索引)。
    3. 初始化 DPdp[1]=1dp[1] = 1,表示 c=1c=1 时的方案数。
    4. 按段转移:对每个段 [lst,i][lst, i],先计算前缀和 sumsum,然后对每个合法的 jjr2[i]<jr1[i]r2[i] < j \le r1[i])更新 dp[j]dp[j]: [ dp[j] = dp[j] \cdot \text{qry1}(lst, i, t[j]) + sum[j-1] \cdot \text{qry2}(lst, i, t[j], t[j-1]) ] 其中第一项对应 cc 不变的情况,第二项对应 cc 增加 11 的情况。
    5. 最终答案j=1mdp[j]\sum_{j=1}^{m} dp[j],其中 mmtt 的大小。

    第九步:复杂度分析

    • 离散化:O((n+m)log(n+m))O((n+m) \log (n+m))
    • 每个段内的转移:O(段数×m)O(\text{段数} \times m),其中段数 O(m)O(m)m1000m \le 1000
    • 总复杂度:O(m2+nlogn)O(m^2 + n \log n),在题目限制下可行

    第十步:完整代码(已提供)

    #include<bits/stdc++.h>
    using namespace std;
    #define MOD         998244353
    #define speMOD      2933256077ll
    #define int         long long
    #define pii         pair<int,int>
    #define all(v)      v.begin(),v.end()
    #define pb          push_back
    #define REP(i,b,e)  for(int i=(b);i<(int)(e);++i)
    #define over(x)     {cout<<(x)<<endl;return;}
    #define lowbit(x)   ((x)&(-(x)))
    #define cntbit(x)   __builtin_popcount(x)
    #define rf(v)       shuffle(all(v),sd);
    #define deal(v)     sort(all(v));v.erase(unique(v.begin(),v.end()),v.end())
    #define lbound(v,x) (int)(lower_bound(all(v),x)-v.begin())
    
    int qpow(int a,int b,int m=MOD,int res=1){
        a%=m;
        while(b>0)res=(b&1)?(res*a%m):(res),a=a*a%m,b>>=1;
        return res;
    }
    
    int fac[2000005],inv[2000005];
    void init(int n){
        fac[0]=inv[0]=1;
        REP(i,1,n+1)fac[i]=fac[i-1]*i%MOD;
        inv[n]=qpow(fac[n],MOD-2);
        for(int i=n-1;i>=1;--i)inv[i]=inv[i+1]*(i+1)%MOD;
    }
    
    int binom(int x,int y){
        return x<y||y<0? 0:fac[x]*inv[y]%MOD*inv[x-y]%MOD;
    }
    
    struct restr{
        int l,r,k;
    };
    
    int n,m;
    int r1[1000005],r2[1000005];
    int dp[10005],sum[10005];
    
    int qry1(int l,int r,int k){
        if(k<l)return qpow(k+1,r-l+1);
        if(k>r)return fac[r+1]*inv[l]%MOD;
        return fac[k+1]*inv[l]%MOD*qpow(k+1,r-k)%MOD;
    }
    
    int qry2(int l,int r,int k,int x){
        return (qry1(l,r,k)+MOD-qry1(l,r,x))%MOD;
    }
    
    void Main() {
        cin>>n>>m;
        vector<int>v;
        vector<restr>a;
        vector<int>t;
        
        REP(i,0,m){
            int k,l,r;
            cin>>k>>l>>r;
            --l;
            if(l>=0) v.pb(l);
            if(r<n) v.pb(r);
            t.pb(k);
            a.pb({l,r,k});
        }
        
        v.pb(n-1);
        t.pb(-1);
        t.pb(n-1);
        
        deal(v);
        deal(t);
        
        // 初始化 r1 和 r2
        for(auto i : v) {
            r1[i] = (int)t.size() - 1;
            r2[i] = 0;
        }
        
        // 处理约束
        for(auto i : a){
            if(i.k == n-1 && i.r < n-1) over(0)
            if(i.l >= 0) r1[i.l] = min(r1[i.l], lbound(t, i.k));
            if(i.r < n) r2[i.r] = max(r2[i.r], lbound(t, i.k));
        }
        
        int lst = 0;
        int msize = (int)t.size();
        dp[1] = 1;
        REP(i, 2, msize+1) dp[i] = 0;
        
        // DP 转移
        for(auto i : v){
            REP(j, 1, msize) sum[j] = dp[j];
            REP(j, 2, msize) (sum[j] += sum[j-1]) %= MOD;
            
            REP(j, 1, msize){
                if(j > r2[i] && j <= r1[i]){
                    dp[j] = (dp[j] * qry1(lst, i, t[j]) + sum[j-1] * qry2(lst, i, t[j], t[j-1])) % MOD;
                } else {
                    dp[j] = 0;
                }
            }
            lst = i + 1;
        }
        
        int res = 0;
        REP(j, 1, msize+1) (res += dp[j]) %= MOD;
        cout << res << endl;
    }
    
    void TC() {
        init(1000000);
        int tc = 1;
        cin >> tc;
        while(tc--){
            Main();
            cout.flush();
        }
    }
    
    signed main() {
        return cin.tie(0), cout.tie(0), ios::sync_with_stdio(0), TC(), 0;
    }
    

    总结

    本题的核心在于:

    1. 发现 bi=iLISib_i = i - \text{LIS}_i 的关系
    2. 利用 LIS 序列每次只能 +1+1 或不变的性质
    3. 设计 DP 状态 dp[i][j]dp[i][j] 表示前 ii 个位置、LISi=j\text{LIS}_i = j 的排列数
    4. mm 个约束转化为对 jj 的区间限制
    5. 使用分段转移和封闭形式优化到 O(m2+nlogn)O(m^2 + n \log n)
    • 1

    信息

    ID
    6638
    时间
    1000ms
    内存
    256MiB
    难度
    2
    标签
    递交数
    1
    已通过
    1
    上传者