普通DP——逆序对构造(CF-Abnormal Permutation Pairs (easy version))
普通DP——逆序对构造(CF-Abnormal Permutation Pairs (easy version))
题目中说到有两个序列p,q:
为什么要分成两部分呢,首先为了保证p比q小,所以从前往后比大小,直到比小的时候,这样我们就可以对前一半和后一半进行分析。
我先顺应大佬的思路来想:(先对后一半部分进行分析)
在和确定的情况下(),同时我们肯定在i之前的字符p和q序列是一样的。这样我们对后面一半进行分析因为这个位置的字母导致q这个序列比p这个序列的逆序对的数量多个(和的值分别意思是在后一半段的序列中分别是排名大小是和的,因为前一半段都一样我们可以排除前一半段的影响因素)。
为了达到说明
这个时候说明我们在知道和的时候可以直接通过控制和的大小来算出到底有多少种。
那么重点来了怎么算出一个长度为 的序列的逆序对数为的时候到底有多少中排列呢?
首先我们这样考虑当只有n个字符的时候,他们逆序对数量为m,我拿出一个比这个n个字符都大的字符g,假如我插入到第h个位置那么原本n个字符被g字符分成了两半,g前面有h-1个字符,g后面有n-h+1个字符,那么由于g的插入创造出了n-h+1个逆序对。原本m个逆序对现在变成了m+n-h+1个逆序对。
顺应这个思路很容易发现前面这个问题完全可以用来构造:
f[0][0] = 1; for(int i = 0; i <= n*(n-1)/2; i++) s[0][i] = 1; for(int i = 1; i <= n; i++) { for(int j = 0; j <= n*(n-1)/2; j++) { if(j-i>= 0) f[i][j] = s[i-1][j]-s[i-1][j-i]; else f[i][j] = s[i-1][j]; s[i][j] = (j?s[i][j-1]:0)+f[i][j]; } }
首先我们需要两个数组来解决长度n的序列有0,1,2...n-1个逆序对可以形成多少种排列的问题:
-
表示长度为i有j个逆序对的排列到底有多少种
-
表示
首先注意初始化
-
在长度为0的序列逆序对为0的排列有一种(就是空)
-
然后由于来进行初始化得出
初始化后可以循环计算,第一层循环的意思是这个序列长度为i,第二个循环意思是这个长度为i的序列有j个逆序对,这样的话假如说原本长度为i-1的序列由于第i个字母的加入可以创造出个逆序对。所以只能从转移得到结果。
所以根据前缀和可以写出
f[i][j] = s[i-1][j]-s[i-1][j-i];
但是由于j-i会小于0所以它最多也只能从转移过来了
然后下面这句话求的是长度为i的字符串有个逆序对的
s[i][j] = (j?s[i][j-1]:0)+f[i][j];
#include<iostream> #include<string> using namespace std; int n,mod,f[55][2005]={1},s[55][2005]={1},ans[55]; int main() { cin >> n >> mod; f[0][0] = 1; for(int i = 0; i <= n*(n-1)/2; i++) s[0][i] = 1; for(int i = 1; i <= n; i++) { for(int j = 0; j <= n*(n-1)/2; j++) { if(j-i>= 0) f[i][j] = (s[i-1][j]-s[i-1][j-i]+mod)%mod; else f[i][j] = s[i-1][j]%mod; s[i][j] = ((j?s[i][j-1]:0)+f[i][j])%mod; } } for(int i = 1; i <= n; i++) { for(int j = 1; j <= i; j++) { for(int z = j+1; z <= i; z++) { for(int g = 0; g <= i*(i-1)/2; g++) { int t = g-(z-j)-1; if(t < 0) continue; else (ans[i] += 1ll*f[i-1][g]*s[i-1][t]%mod)%=mod; } } } } for(int i = 2; i <= n; i++) ans[i] = (ans[i] + 1ll*ans[i-1]*i%mod)%mod; cout << ans[n]; }