# 牛客挑战赛46_C题排列(前缀优化DP)

牛客挑战赛46_C题排列(前缀优化DP)

传送门

题意:给定超级逆序对为k,求长度为n的排列的方案数;

超级逆序对:满足 i<j 且 a[i]>a[j]+1 的二元组 (i,j);

题解:若题目是逆序对个数为k,对于从1到n的每一个数,从小至大的插入数组,都有(0,i-1)的安排方案,枚举每一个数,记忆化搜索一下即可,现在多了一个超级逆序对,即每个数最多贡献i-2的超级逆序对的个数,并且会因为不同的i-1的位置影响不同的转移。所以我们可以给dp加一维i-1的位置信息,并加一层循环即可。空间是n ^ 3,时间是n ^ 4。爆空间,且超时。空间考虑滚动数组优化,并且我们容易发现转移具有单调性,所以利用预处理前缀和优化dp,达到n ^ 2空间,n ^ 3时间。

#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
const ll mod=998244353;
ll n,k,lin[507][507],dp[507][507],pre[507][507];
ll pow(ll x,ll n,ll mod){
    ll res=1;
	while(n>0){
	   if(n%2==1){
	   	 res=res*x;
	   	 res=res%mod;
	   }
	   x=x*x;
	   x=x%mod;
	   n>>=1;
	}
	return res;
}
ll inv(ll x){
	return pow(x,mod-2,mod);
}
int main(){
	cin>>n>>k;
	lin[0][1]=1;
	for(int i=2;i<=n;i++){
		for(int j=0;j<=k;j++){
			for(int c=1;c<i;c++){
				pre[j][c]=(pre[j][c-1]+lin[j][c])%mod;
			}
		}
		memset(dp,0,sizeof(dp));
		for(int j=0;j<=k;j++){
			for(int d=1;d<=i;d++){
				dp[j][d]=(dp[j][d]+pre[j-i+d][d-1])%mod;
				dp[j][d]=(dp[j][d]+(pre[j-i+d+1][i-1]-pre[j-i+d+1][d-1]+mod)%mod)%mod;
			}
		}
		for(int j=0;j<=k;j++){
			for(int d=1;d<=i;d++){
				lin[j][d]=dp[j][d];
			}
		}
	}
	ll ans=0;
	for(int i=1;i<=n;i++){
		ans+=lin[k][i];
		ans%=mod;
	}
	printf("%lld\n",inv(ans));
}
posted @ 2020-12-30 18:00  ccsu_madoka  阅读(63)  评论(0编辑  收藏  举报