一、题目描述

  设 $\pi (x)$ 为全排列 $x$ 的逆序对数。

  给定 $n,m$,求有多少对长度为 $n$ 的排列 $p,q$,使得 $p$ 的字典序小于 $q$,且 $\pi (p)>\pi (q)$

  答案对 $m$ 取模。数据范围:$1\le n\le 500,1\le m\le 10^9$。


 二、解题思路:

  一开始列出计算式个人感觉是最难的一步,后面化简多半就是套路了。

  因为字典序相较于逆序对数要简单一些,所以我们先考虑字典序。

  枚举排列 $p$ 在第 $i$ 个位置第一次比 $q$ 小,枚举 $i$ 位置上 $p,q$ 的大小,再考虑逆序对数。

  注意到前 $i-1$ 位不会对逆序对数之差造成任何影响,无论是内部还是对外都是这样。

  对于后面 $n-i$ 位的部分显然应该计数 $dp$,因为没有其他思路,数据范围也提示你了。

  设 $f_{i,j}$ 表示长度为 $i$ 的排列对 $(p,q)$ 满足 $\pi(p)-\pi(q)$ 的数量为 $j$ 的方案数。

   为什么这样设状态呢?因为我们对于每一个长度的 $(p,q)$ 都要统计答案,而且这样很好转移。

  显而易见的,$ans=\sum_{i=1}^n{n\choose n-i}\times (n-i)! \times (\sum_{j=1}^{lim}\sum_{p=1}^i\sum_{q=p+1}^if_{i-1,j-p+q})$

  注意这里枚举的 $i$ 与前面意义不一样,这里的 $i$ 是枚举的后面一部分的长度。

  显然我们需要做到对 $f$ 数组的 $O(1)$ 转移。先推一下式子:

  $f_{i,j}=\sum_{p=1}^i\sum_{q=1}^if_{i-1,j-p+q}$

  $=\sum_{p-q=1-i}^{i-1}f_{i-1,j-(p-q)}\times (i-\left| p-q\right|)$

  $=\sum_{\mid d \mid =0}^{i-1}f_{i-1,j-d}\times (i-\left|d\right|)$

  $=\sum_{d=1}^if_{i-1,j+d}\times (i-d)+\sum_{d=0}^{i-1}f_{i-1,j-d}\times (i-d)$

  $=S1_{i,j}+S2_{i,j}$($S1_{i,j},S2_{i,j}$ 分别代替上个式子中的两个部分)

  $S1_{i,j}=\sum_{d=1}^{i}f_{i-1,j+d}\times i-\sum_{d=1}^{i}f_{i-1,j+d}\times d$

  $=\sum_{d=1}^{i}f_{i-1,j+d}\times (i+j)-\sum_{d=1}^{i}f_{i-1,j+d}\times (j+d)$

  $S2_{i,j}=\sum_{d=0}^{i-1}f_{i-1,j-d}\times i-\sum_{d=0}^{i-1}f_{i-1,j-d}\times d$

  $=\sum_{d=0}^{i-1}f_{i-1,j-d}\times (i-j)+\sum_{d=0}^{i-1}f_{i-1,j-d}\times (j-d)$

  这里的转化比较有意思,将系数化来与枚举的系数相同(如 $j+d,j-d$),便于前缀和优化。

  令 $G1_{i,j}=\sum_{k=-lim}^jf_{i,k},G2_{i,j}=\sum_{k=-lim}^jf_{i,k}\times k$,那么有:

  $S1_{i,j}=(i+j)\times (G1_{i-1,i+j}-G1_{i-1,j})+G2_{i-1,j}-G2_{i-1,j+i}$

  $S2_{i,j}=(i-j)\times (G1_{i-1,j}-G1_{i-1,j-i})+G2_{i-1,j}-G2_{i-1,j-i}$

  至此,我们可以 $O(1)$ 转移方程,总时间复杂度 $O(n^3)$。

  还有一点需要注意的就是 $f_{i,j}$ 中的 $j$ 可以是负数,注意数组下标加上一个定值来储存。代码中用 $M$ 来表示。


 三、完整代码

 1 #include<iostream>
 2 
 3 #define N 510
 4 #define M 130000
 5 #define ll long long
 6 #define rep(i,l,r) for(ll i=l;i<=r;i++)
 7 
 8 using namespace std;
 9 
10 ll n,mod,ans,fac[N],C[N][N];
11 ll f[M<<1],s1[M<<1],s2[M<<1],g1[M<<1],g2[M<<1];
12 
13 void calc(ll x){
14     
15     ll lim=x*(x-1)/2,cnt=0;
16     
17     rep(i,M-lim-x,M+lim+x){
18         g1[i]=(g1[i-1]+f[i])%mod;
19         g2[i]=(g2[i-1]+f[i]*i)%mod;
20     }
21     
22     rep(i,M-lim,M+lim){
23         s1[i]=(x+i)*(g1[i+x]-g1[i])+g2[i]-g2[i+x];
24         s2[i]=(x-i)*(g1[i]-g1[i-x])+g2[i]-g2[i-x];
25     }
26     
27     rep(i,M-lim,M+lim){
28         s1[i]=(s1[i]%mod+mod)%mod;
29         s2[i]=(s2[i]%mod+mod)%mod;
30     }
31     
32     rep(i,M+1,M+lim) (cnt+=s1[i])%=mod;
33     (ans+=C[n][n-x]*fac[n-x]%mod*cnt%mod)%=mod;
34     
35     rep(i,M-lim,M+lim) f[i]=(s1[i]+s2[i])%mod;
36     
37 }
38 
39 signed main(){
40     
41     ios::sync_with_stdio(false);
42     cin.tie(0); cout.tie(0);
43     
44     cin>>n>>mod; fac[0]=f[M]=1;
45     rep(i,1,n) fac[i]=fac[i-1]*i%mod;
46     
47     rep(i,0,n) C[i][0]=1;
48     rep(i,0,n) rep(j,1,i) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
49     
50     rep(i,2,n) calc(i);
51     cout<<ans<<'\n';
52     
53     return 0;
54     
55 }

四、写题心得:

  感觉 $feecle$ 选的题都还挺有趣,拜谢 。

  并且掌握了新的推式子技巧和方法,很高兴。

  忘了说,顺便水了一道蓝题,$CF1542E1$。

posted on 2024-02-02 21:57  trh0630  阅读(4)  评论(0编辑  收藏  举报