【题解】逆序排列 [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) 之后新构成的逆序对数 (kin [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
",dp[n][K]);
}