【题解】逆序排列 [51nod1020]

【题解】逆序排列 [51nod1020]

传送门:逆序排列 \([51nod1020]\)

【题目描述】

\(T\) 组测试点,每一组给出 \(2\) 个整数 \(n\)\(k\),在 \([1,n]\)\(n\) 个数字的全排列中,逆序数为 \(k\) 的排列种数,答案对 \(1e9+7\) 取模。

【样例】

样例输入:
1
4 3

样例输出:
6

【数据范围】

\(100\%\) \(1 \leqslant T \leqslant 10000,\) \(1 \leqslant N \leqslant 1000,\) \(1 \leqslant K \leqslant 20000\)

【分析】

\(dp[i][j]\) 表示 \([1,i]\) 的排列中逆序对数为 \(j\) 的排列种数,为方便枚举,用 \(ss[i]\) 表示长度为 \(i\) 的排列总数\((ss[i]=min\{K,\frac{i*(i-1)}{2}\}\)

对于数字 \(i\) 来说,在一个 \([1,i-1]\) 的排列中它有 \(i\) 个位置可以加入,加下 \(i\) 之后新构成的逆序对数 \(k\in [0,i-1]\)
那么 \(dp\) 方程就出来了:\(dp[i][j]+=dp[i-1][j-k]\),其中 \(0 \leqslant j \leqslant ss[i],\) \(0 \leqslant j-k \leqslant ss[i-1]\) ,所以 \(max\{0,j-ss[i-1]\} \leqslant k \leqslant min\{j,i-1\}\)
\(dp[i][j]=\sum_{k=max\{0,j-ss[i-1]\}}^{min\{j,i-1\}}dp[i-1][j-k]\)

时间复杂度过高,要用前缀和优化一下,\(dp[i][j]=S[j-max(0,j-ss[i-1])]-S[j-min(i-1,j)-1]\),其中 \(S[i]=\sum_{j=0}^{i}dp[i-1][j]\)

但是前缀和下边界可能会取到 \(0\),减了 \(1\) 之后就成了 \(-1\)\(S[-1]\)是不存在的,所以每一次写 \(S[x]\) 都要改成 \(S[x+1]\)

还要先预处理答案,询问时直接 \(O(1)\) 查询。

时间复杂度为 \(O(NK)\)

【Code】

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=1003,M=2e4+3,P=1e9+7;
int T,n,K,S[M],ss[N],dp[N][M];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
int main(){
    n=1000,K=20000;
    for(Re i=2;i<=n;++i){
    	ss[i]=i*(i-1)>>1;
    	if(ss[i]>K){for(Re j=i;j<=n;++j)ss[j]=K;break;}
    }
    for(Re i=1;i<=n;++i)dp[i][0]=1;
    for(Re j=0;j<=ss[2];++j)(S[j+1]=S[j-1+1]+dp[1][j])%=P;
    for(Re i=2;i<=n;++i){
    	for(Re j=1;j<=ss[i];++j)(dp[i][j]=(S[j-max(0,j-ss[i-1])+1]-S[j-min(i-1,j)-1+1]+P)%P)%=P;
    	for(Re j=0;j<=ss[i+1];++j)(S[j+1]=S[j-1+1]+dp[i][j])%=P;
    }
    in(T);while(T--)in(n),in(K),printf("%d\n",dp[n][K]);
}
posted @ 2019-08-13 18:45  辰星凌  阅读(250)  评论(0编辑  收藏  举报