1 条题解
-
0
题目重述
定义 为冒泡排序对数组 排序所需的轮数(即外层循环次数)。
已知排列 长度为 ,定义 。给定 个约束 ,要求满足:
设 ,则 。
求满足条件的排列个数,模 。
第一步:关键结论——冒泡排序轮数与 LIS 的关系
对于排列 的前缀 ,有: [ b_i = i - \text{LIS}([p_1..p_i]) ] 其中 是最长递增子序列的长度。
验证(以 为例):
- , 的 LIS=1, ✓
- , 的 LIS=1, ✓
- , 的 LIS=2, ✓
- , 的 LIS=2, ✓
因此: [ b_i \le k \iff i - \text{LIS}_i \le k \iff \text{LIS}_i \ge i - k ]
第二步:转化约束
原约束:
代入 : [ b_y \le k_j \iff y - \text{LIS}_y \le k_j \iff \text{LIS}_y \ge y - k_j ]
所以每个约束 等价于:
在前缀 LIS 序列 中,满足 的 的个数在 之间。
第三步:LIS 序列的性质
对于排列 ,其前缀 LIS 长度序列 满足:
- (每次最多增加 )
证明:加入新元素 后,LIS 要么不变,要么增加 (如果 可以接在某个长度为 的递增子序列后面)。
第四步:转化为序列计数问题
我们需要统计长度为 的整数序列 的个数,满足:
- 对于每个约束 ,
并且每个这样的 序列对应多少个排列?
已知结论:给定 序列,满足它的排列个数为: [ \prod_{i=1}^{n} (i - c_{i-1}) \quad \text{其中} \quad c_0 = 0 ] 因为当 时, 必须小于所有 LIS 的末尾最小值;当 时, 必须大于某个值。更准确的公式(来自经典结果): [ \text{count} = \prod_{i=1}^{n} (i - c_{i-1}) ] 其中 ,且要求 ,。
第五步:DP 状态设计
定义 表示前 个位置, 时的总排列数(已乘上系数)。
转移:
- 如果 ,则 可以是 或 (如果 )
- 乘上系数 (因为新元素有这么多选择)
即: [ dp[i][j] = dp[i-1][j] \cdot (i - j) + dp[i-1][j-1] \cdot (i - (j-1)) ] 边界:, for 。
第六步:处理约束(标程的核心优化)
标程中将 个位置划分为若干段,每段内 的取值区间 使得所有约束的边界条件不变。
定义 数组为所有出现过的 以及 和 的排序去重结果。
对于每个段 ( 来自 数组,是约束的边界),我们只需要知道:- 最小允许的 值(由 给出)
- 最大允许的 值(由 给出)
其中:
- 表示位置 处, 必须至少为某个值(通过 转换)
- 表示位置 处, 必须不超过某个值
标程中的
qry1和qry2函数计算的是在区间 内, 值固定时,所有可能的排列数乘积的封闭形式。
第七步:转移方程的封闭形式
在一个区间 内,如果 保持为常数 ,那么: [ \prod_{i=L}^{R} (i - j) = \frac{(R-j)!}{(L-1-j)!} \quad \text{如果 } L > j ] 如果 ,则乘积中包含 ,整个为 。
标程中的
qry1(l, r, k)计算的是: [ \text{qry1}(l, r, k) = \prod_{i=l}^{r} \max(i - k, 1) \quad \text{的某种变体} ] 实际上它计算的是:- 如果 ,则返回 (因为所有 )
- 如果 ,则返回 (因为 恒为正且递增)
- 否则分段计算
qry2则是两个qry1的差,用于处理 值变化的情况。
第八步:标程的算法流程
- 读入并离散化:将所有的 和 以及 存入 ,去重排序。将所有 以及 、 存入 ,去重排序。
- 预处理边界:对每个位置 (来自 ),计算 (最小允许的 的索引)和 (最大允许的 的索引)。
- 初始化 DP:,表示 时的方案数。
- 按段转移:对每个段 ,先计算前缀和 ,然后对每个合法的 ()更新 : [ dp[j] = dp[j] \cdot \text{qry1}(lst, i, t[j]) + sum[j-1] \cdot \text{qry2}(lst, i, t[j], t[j-1]) ] 其中第一项对应 不变的情况,第二项对应 增加 的情况。
- 最终答案:,其中 是 的大小。
第九步:复杂度分析
- 离散化:
- 每个段内的转移:,其中段数 ,
- 总复杂度:,在题目限制下可行
第十步:完整代码(已提供)
#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; }
总结
本题的核心在于:
- 发现 的关系
- 利用 LIS 序列每次只能 或不变的性质
- 设计 DP 状态 表示前 个位置、 的排列数
- 将 个约束转化为对 的区间限制
- 使用分段转移和封闭形式优化到
- 1
信息
- ID
- 6638
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 2
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者