1. 程式人生 > 實用技巧 ># 牛客挑戰賽46_C題排列(字首優化DP)

# 牛客挑戰賽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的位置資訊,並加一層迴圈即可。空間是n3,時間是n4。爆空間,且超時。空間考慮滾動陣列優化,並且我們容易發現轉移具有單調性,所以利用預處理字首和優化dp,達到n2空間,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));
}