P1080 旧词 官方题解
Gioush OJ · P1080 旧词
【部分分:测试点 1∼41\sim 41∼4】
- 枚举下阕词节的全部排列,并依次与上阕词节配对。
- 直接统计昂句与抑句的数量差,判断它是否等于 。
- 时间复杂度为 ,空间复杂度为 。
【部分分:测试点 5∼95\sim 95∼9】
-
枚举排列的瓶颈仍然是方案数。把全部 个音迹按强度从小到大排序,逐个决定它暂时等待,还是与更早出现的异类音迹配对。
-
记 分别为前 个音迹中上阕、下阕音迹的数量。
-
定义 为处理完前 个音迹,已经完成 组配对,其中恰有 组昂句的方案数。
-
初始时 ,其余不合法状态均为 。
-
当前音迹可以先不配对,因此始终有
- 若第 个音迹来自上阕,那么此前尚未配对的下阕音迹有 个。与其中任意一个配对都会形成昂句,因此
- 若第 个音迹来自下阕,那么此前尚未配对的上阕音迹有 个。此时形成的是抑句,因此
- 若 为偶数,令 ,最终答案为 ;否则答案为 。
- 状态数为 ,每个状态只有常数次转移。时间复杂度为 ,滚动第一维后空间复杂度为 。
【部分分:测试点 10∼1410\sim 1410∼14】
- 此时 ,所有配对都必须是昂句。
- 将两列音迹分别排序,记
按照 从小到大的顺序配对。轮到 时,前面已经用去 个较弱下阕词节,因此还有 种选择。
- 因而答案为
- 排序后线性扫描即可,时间复杂度为 。
【部分分:测试点 15∼1915\sim 1915∼19】
- 两类音迹在排序后交错出现。令 ,若 为奇数,答案仍为 。
- 先考虑
把 与 配对时,形成昂句当且仅当 。
- 这与逆排列中的弱超越位置一一对应。令 为长度为 的排列中恰有 个弱超越位置的排列数,则
- 在上一种交错顺序中,恰有 组昂句的答案为 。
- 再考虑
此时形成昂句当且仅当 ,也就是严格超越数。它与弱超越数的下标相差 ,答案为 。
- 时间复杂度为 。使用滚动数组后,空间复杂度为 。
【正解】
- 设一组编排含有 组昂句。由于昂句与抑句数量之和为 ,需要满足
- 因而 。若 为奇数,答案为 。
- 将两列音迹分别从小到大排序,并继续记
- 定义 为只考虑前 个上阕词节,选出其中 个,并为它们钦定互不相同的较弱下阕词节的方案数。
- 边界为 ,其余不合法状态为 。不钦定 ,或者把它与一个尚未使用的较弱下阕词节钦定为昂句,得到
-
转移范围为 、,按照 递增的顺序计算。
-
钦定 组昂句以后,其余 组可以任意配对。记
- 这里 统计的是“完整编排与其中被钦定的 组昂句”组成的二元组。
- 记 为恰有 组昂句的完整编排数。每种这样的编排可以从 组昂句中选出 组加以钦定,所以
- 第一种做法直接从 开始向下计算。
- 中还包含实际拥有更多昂句的编排。实际含有 组昂句的编排已经由 求出,并在 中出现 次。
- 因此逐项扣除重复统计:
-
依次求到 即可。这一过程只是按照已知重复次数倒序扣除,不需要使用反演。
-
第二种做法从
出发,把它看作一个二项式变换。
- 使用二项式反演,可以直接得到
-
这个式子正好消去了“额外钦定昂句”带来的重复统计。
-
把 的表达式代入反演式,并交换求和顺序。对固定的 ,它的系数为
- 利用 ,这个系数等于
它只有在 时为 ,其余时候均为 ,所以反演式成立。
- 两种做法共享同一个 ,区别只在于如何从 恢复恰好计数 。
- 预处理阶乘、逆阶乘与组合数后,转移和恢复答案都可以在 时间内完成。
- 空间复杂度为 。若滚动 的第一维,可以降为 。
- 所有运算均对 取模。
【参考代码】
/*Author:EhundateghDate:2026/8/31Name:verse.cppYou steal,I kill.*/#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 2001using namespace std;const int Mod=998244353; int c,T,A[MAXN],B[MAXN],n,k,Larger[MAXN],Dp[MAXN][MAXN],R,F[MAXN];int Inv[MAXN]={0,1},Fact[MAXN]={1},Fact_Inv[MAXN]={1}; inline int Add(int a,int b){return (a+=b)>=Mod?a-Mod:a;}inline int Mul(int a,int b){return 1ll*a*b%Mod;}inline int Del(int a,int b){return (a-=b)>=0?a:a+Mod;} void Init(){ for(int i=1;i<=MAXN-1;i++) Fact[i]=Mul(Fact[i-1],i); for(int i=2;i<=MAXN-1;i++) Inv[i]=Del(Mod,Mul(Inv[Mod%i],Mod/i)); for(int i=1;i<=MAXN-1;i++) Fact_Inv[i]=Mul(Inv[i],Fact_Inv[i-1]);} int C(int n,int m){return Mul(Mul(Fact[n],Fact_Inv[m]),Fact_Inv[n-m]);} void Solve(){ scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d",&A[i]); for(int i=1;i<=n;i++) scanf("%d",&B[i]); if((n+k)&1){puts("0");return;} R=(n+k)/2; sort(A+1,A+n+1);sort(B+1,B+n+1); int Pointer=1; for(int i=1;i<=n;i++){ while(Pointer<=n&&A[i]>B[Pointer]) Pointer++; Larger[i]=Pointer-1; } memset(Dp,0,sizeof(Dp));memset(F,0,sizeof(F)); Dp[0][0]=1; for(int i=1;i<=n;i++){ for(int j=0;j<=min(i,Larger[i]);j++){ Dp[i][j]=Dp[i-1][j]; if(j&&Larger[i]-j+1>0) Dp[i][j]=Add(Dp[i][j],Mul(Dp[i-1][j-1],Larger[i]-j+1)); } } for(int i=n;i>=R;i--){ F[i]=Mul(Dp[n][i],Fact[n-i]); for(int j=n;j>i;j--) F[i]=Del(F[i],Mul(F[j],C(j,i))); } printf("%d\n",F[R]); return;} int main(){ freopen("verse.in","r",stdin); freopen("verse.out","w",stdout); Init();scanf("%d%d",&c,&T); while(T-->0) Solve(); return 0;}