普通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]; }