日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【刷题记录】排列dp

發布時間:2023/12/3 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【刷题记录】排列dp 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • [AtCoder-ABC209-f] Deforestation
  • [AtCoder-Educational DP Contest-T]Permutation
  • 「JOI Open 2016」摩天大樓
  • topcoder srm 489 div1 lev3 : AppleTrees
  • [CodeForces-626F] Group Projects
  • [TopCoder] Seatfriends
  • 小結

[AtCoder-ABC209-f] Deforestation

考慮相鄰的兩棵樹,先砍 i?1i-1i?1 再砍 iii,花費 hi?1+2?hih_{i-1}+2*h_ihi?1?+2?hi?,先砍 iii 再砍 i?1i-1i?1,花費 2?hi?1+hi2*h_{i-1}+h_i2?hi?1?+hi?

也就是說,后砍的樹貢獻次數要多一次。貪心地有 越高的樹越先砍。

f(i,j):f(i,j):f(i,j): 所有排列中在只考慮前 iii 個樹的情況下, iii 樹是第 jjj 個被砍掉的數量。

  • hi=hi?1h_i=h_{i-1}hi?=hi?1?,無所謂先后。

    f(i,j)=∑k=1i?1f(i?1,k)f(i,j)=\sum_{k=1}^{i-1}f(i-1,k)f(i,j)=k=1i?1?f(i?1,k)

  • hi>hi?1h_i>h_{i-1}hi?>hi?1?

    f(i,j)=∑k=ji?1f(i?1,k)f(i,j)=\sum_{k=j}^{i-1}f(i-1,k)f(i,j)=k=ji?1?f(i?1,k)

  • hi<hi?1h_i<h_{i-1}hi?<hi?1?

    f(i,j)=∑k=1j?1f(i?1,k)f(i,j)=\sum_{k=1}^{j-1}f(i-1,k)f(i,j)=k=1j?1?f(i?1,k)

前綴和優化即可。

注意:可能你會疑惑為什么求和上限不是 nnn。如果是 nnn 其實想一下就知道會算重,但又會疑惑為什么這樣不會算重。下面給出兩種解釋(本質一樣)。

  • 這個第 jjj 個被砍掉的含義是,假設已知最后 nnn 棵樹的砍順序,把 1~i1\sim i1i 的樹的砍樹順序單獨拎出來組成長度為 iii 的順序,從小到大排序后,iii 樹在這個小排列中是第 jjj 個被砍的。
  • Oxide\text{Oxide}Oxide 解釋:這是個相對過程,后面的樹一旦插入 jjj,相當于把前面的樹砍順序在 jjj 及以后的都再往后移了一個。
#include <cstdio> #define maxn 4005 #define int long long #define mod 1000000007 int n; int h[maxn]; int dp[maxn][maxn];signed main() {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ )scanf( "%lld", &h[i] );h[0] = h[1], dp[0][0] = 1;for( int i = 1;i <= n;i ++ ) {for( int j = 1;j <= i;j ++ ) {if( h[i] == h[i - 1] )dp[i][j] = dp[i - 1][i - 1];else if( h[i] > h[i - 1] )dp[i][j] = ( dp[i - 1][i - 1] - dp[i - 1][j - 1] + mod ) % mod;elsedp[i][j] = dp[i - 1][j - 1];}for( int j = 1;j <= i;j ++ )dp[i][j] = ( dp[i][j] + dp[i][j - 1] ) % mod;}printf( "%lld\n", dp[n][n] );return 0; }

[AtCoder-Educational DP Contest-T]Permutation

vjudge

這道題和上一道題目是同一個類型,dpdpdp 定義以及優化也是一樣的。

#include <bits/stdc++.h> using namespace std; #define int long long #define mod 1000000007 #define maxn 3005 int f[maxn][maxn]; char s[maxn]; int n; /* f(i,j):考慮到i位放j 前面都已經滿足s的符號限制 的排列數量 s[i] = '<' f(i+1,j) <- f(i,k) k<j s[i] = '>' f(i+1,j) <- f(i,k) k>j */ signed main() {scanf( "%lld %s", &n, s + 1 );for( int i = 1;i <= n;i ++ ) f[1][i] = i;for( int i = 1;i < n;i ++ ) {for( int j = 1;j <= n;j ++ ) {if( s[i] == '<' )f[i + 1][j] = f[i][j - 1] % mod;elsef[i + 1][j] = ( f[i][i] - f[i][j - 1] + mod ) % mod;}for( int j = 1;j <= n;j ++ )( f[i + 1][j] += f[i + 1][j - 1] ) %= mod; }printf( "%lld\n", f[n][n] );return 0; }

「JOI Open 2016」摩天大樓

LOJ#2743

AAA 從小到大排序。考慮微元法。

答案排列相鄰的一對 Ai,AjA_i,A_jAi?,Aj?,產生的貢獻可以表示為 ∑k=ji?1Ak+1?Ak\sum_{k=j}^{i-1}A_{k+1}-A_kk=ji?1?Ak+1??Ak?

我們可以提前計算 Ak+1?AkA_{k+1}-A_{k}Ak+1??Ak? 對答案的貢獻次數。

f(i,j,k,d):f(i,j,k,d):f(i,j,k,d): 放了前 iii 個數,產生了 jjj 個互相獨立的連續段,總貢獻為 kkk,排列的首尾(下面稱為墻)被占了 ddd 個。

則可以算出當前 Ai+1?AiA_{i+1}-A_iAi+1??Ai? 的貢獻,即為 (Ai+1?Ai)?(2?j?d)(A_{i+1}-A_i)·(2*j-d)(Ai+1??Ai?)?(2?j?d)

因為有 2?j?d2*j-d2?j?d(墻只能延伸一個方向)個連續段的左右在后面某個時刻會放數,這部分一定會貢獻微元。

t=k+(2?j?d)(Ai+1?Ai)t=k+(2*j-d)(A_{i+1}-A_i)t=k+(2?j?d)(Ai+1??Ai?)

  • i+1i+1i+1 獨立成一段。

    • 獨立成一個中間段,非墻。

      如果一個墻都沒有,那么有 j+1j+1j+1 個空,KaTeX parse error: Expected '}', got '_' at position 7: \text{_?_X___X___}

      如果有一個墻,就少一個空,KaTeX parse error: Expected '}', got '_' at position 8: \text{X_?__X___X___}

      所以合并可以寫成以下形式:

      f(i+1,j+1,t,d)←f(i,j,k,d)?(j+1?d)f(i+1,j+1,t,d)\leftarrow f(i,j,k,d)*(j+1-d)f(i+1,j+1,t,d)f(i,j,k,d)?(j+1?d)

    • 獨立成一個墻段,如果前后兩個墻均未有,則還要考慮是做首還是尾。

      f(i+1,j+1,t,d+1)←f(i,j,k,d)?(2?d)f(i+1,j+1,t,d+1)\leftarrow f(i,j,k,d)·(2-d)f(i+1,j+1,t,d+1)f(i,j,k,d)?(2?d)

  • i+1i+1i+1 做樞紐,合并某兩個段。jjj 個段,有 j?1j-1j?1 個空填入 i+1i+1i+1 然后連接起來。

    f(i+1,j?1,t,d)←f(i,j,k,d)?(j?1)f(i+1,j-1,t,d)\leftarrow f(i,j,k,d)·(j-1)f(i+1,j?1,t,d)f(i,j,k,d)?(j?1)

  • i+1i+1i+1 從某個段的左/右延伸。

    • 普通延伸。

      f(i+1,j,t,d)←f(i,j,k,d)?(2?j?d)f(i+1,j,t,d)\leftarrow f(i,j,k,d)·(2*j-d)f(i+1,j,t,d)f(i,j,k,d)?(2?j?d)

    • 延伸到了墻。

      f(i+1,j,t,d+1)←f(i,j,k,d)?(2?l)f(i+1,j,t,d+1)\leftarrow f(i,j,k,d)·(2-l)f(i+1,j,t,d+1)f(i,j,k,d)?(2?l)

有些轉移還有對 i,j,k,di,j,k,di,j,k,d 的限制,具體可見下面代碼。

#include <bits/stdc++.h> using namespace std; #define int long long #define mod 1000000007 int f[2][102][1002][3]; int a[105], n, L;signed main() {scanf( "%lld %lld", &n, &L );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] );if( n == 1 ) return ! puts("1");sort( a + 1, a + n + 1 );f[0][0][0][0] = 1;for( int i = 0;i < n;i ++ ) {int o = i & 1;memset( f[o ^ 1], 0, sizeof( f[o ^ 1] ) );for( int j = 0;j <= i + 1;j ++ )for( int k = 0;k <= L;k ++ )for( int d = 0;d <= 2;d ++ ) {if( ! f[o][j][k][d] ) continue;int t = k + ( a[i + 1] - a[i] ) * ( j * 2 - d );if( t > L ) continue;( f[o ^ 1][j + 1][t][d] += f[o][j][k][d] * ( j + 1 - d ) ) %= mod;if( d < 2 ) ( f[o ^ 1][j + 1][t][d + 1] += f[o][j][k][d] * ( 2 - d ) ) %= mod;( f[o ^ 1][j][t][d] += f[o][j][k][d] * ( 2 * j - d ) ) %= mod;if( j ) ( f[o ^ 1][j - 1][t][d] += f[o][j][k][d] * ( j - 1 ) ) %= mod;if( d < 2 and j ) ( f[o ^ 1][j][t][d + 1] += f[o][j][k][d] * ( 2 - d ) ) %= mod;}}int ans = 0;for( int i = 0;i <= L;i ++ ) ( ans += f[n & 1][1][i][2] ) %= mod;printf( "%lld\n", ans );return 0; }

topcoder srm 489 div1 lev3 : AppleTrees

Vjudge-AppleTrees

topcoder這是什么煞筆提交方式(無能狂怒

如果我們確定了一個種樹的順序,那么相鄰樹的最小間距也隨之確定。

DDD 減去這個最小間距,就可以看成一個插板問題了。

所以我們要求出對于每一個 LLL,滿足條件 L=∑i=1n?1max?{r(Pi),r(Pi+1)}L=\sum_{i=1}^{n-1}\max\Big\{r(P_i),r(P_{i+1})\Big\}L=i=1n?1?max{r(Pi?),r(Pi+1?)} 的排列數量。

注意到 rrr 最大的 iii 左右種什么數不影響這兩段間隙的最小長度,因為均由 rir_iri? 決定。

所以我們將 rrr 從小到大排序,貢獻由相鄰兩棵樹之間后放入的樹(rrr更大的樹)決定。

f(i,j,k):f(i,j,k):f(i,j,k):iii 棵樹,形成了 jjj 個不相交的排列,排列的代價總和為 kkk 的方案數。

  • i+1i+1i+1 獨立成一個新排列。

    f(i+1,j+1,k)←f(i,j,k)f(i+1,j+1,k)\leftarrow f(i,j,k)f(i+1,j+1,k)f(i,j,k)

  • i+1i+1i+1 延伸某個排列。每個排列還有左右延伸之分。

    f(i+1,j,k+ri+1)←f(i,j,k)?2?jf(i+1,j,k+r_{i+1})\leftarrow f(i,j,k)*2*jf(i+1,j,k+ri+1?)f(i,j,k)?2?j

  • i+1i+1i+1 合并兩個排列。由于每個排列之間沒有敲定順序,所以是任選兩個排列還要區分接法。

    f(i+1,j?1,k+2?ri+1)←f(i,j,k)?Aj2f(i+1,j-1,k+2*r_{i+1})\leftarrow f(i,j,k)*A_{j}^2f(i+1,j?1,k+2?ri+1?)f(i,j,k)?Aj2?

#include <bits/stdc++.h> using namespace std; #define mod 1000000007 long long fac[100005], inv[100005]; long long f[50][50][1700];int qkpow( int x, int y ) {int ans = 1;while( y ) {if( y & 1 ) ans = 1ll * ans * x % mod;x = 1ll * x * x % mod;y >>= 1;}return ans; }void init( int n ) {fac[0] = inv[0] = 1;for( int i = 1;i <= n;i ++ ) fac[i] = fac[i - 1] * i % mod;inv[n] = qkpow( fac[n], mod - 2 );for( int i = n - 1;i;i -- ) inv[i] = inv[i + 1] * ( i + 1 ) % mod; }long long C( int n, int m ) {if( n < m ) return 0;else return fac[n] * inv[m] % mod * inv[n - m] % mod; }class AppleTrees {public :int theCount( int D, vector < int > r ) {int n = r.size();init( D );sort( r.begin(), r.end() );f[0][0][0] = 1;for( int i = 0;i < n;i ++ )for( int j = 0;j <= n;j ++ )for( int k = 0;k <= 1600 and k <= D;k ++ ) {if( ! f[i][j][k] ) continue;( f[i + 1][j + 1][k] += f[i][j][k] ) %= mod;( f[i + 1][j][k + r[i]] += f[i][j][k] * (j << 1) % mod ) %= mod;if( j ) ( f[i + 1][j - 1][k + (r[i] << 1)] += f[i][j][k] * j * (j - 1) % mod ) %= mod;}int ans = 0;for( int i = 1;i <= min( 1600, D );i ++ )ans = ( 1ll * ans + f[n][1][i - 1] * C( D - i + n, n ) % mod ) % mod;return ans;} };

[CodeForces-626F] Group Projects

CodeForces

與上面的摩天大樓類似。

對于每一組,我們只關心最大值和最小值。

將序列從小到大排序后,每組的最大值減去最小值的差值相當于是一段排序后數組的差分和。

f(i,j,k):f(i,j,k):f(i,j,k):iii 個數,當前分成了若干組,其中有 jjj 組還能繼續放數,各組的極值之和為 kkk 的方案數。

當前差分值的貢獻為 (ai+1?ai)?j(a_{i+1}-a_i)*j(ai+1??ai?)?j。記 t=(ai+1?ai)?j+kt=(a_{i+1}-a_i)*j+kt=(ai+1??ai?)?j+k

  • 單獨成一組,且是組的起終點,不能繼續放數:f(i+1,j,t)←f(i,j,k)f(i+1,j,t)\leftarrow f(i,j,k)f(i+1,j,t)f(i,j,k)
  • 新開一個組,且不關閉:f(i+1,j+1,t)←f(i,j,k)f(i+1,j+1,t)\leftarrow f(i,j,k)f(i+1,j+1,t)f(i,j,k)
  • 插入某個組,但不結束那個組,有 jjj 種方式:f(i+1,j,t)←f(i,j,k)?jf(i+1,j,t)\leftarrow f(i,j,k)*jf(i+1,j,t)f(i,j,k)?j
  • 插入某個組,且結束那個組,仍有 jjj 種方式:f(i+1,j,t)←f(i,j,k)?jf(i+1,j,t)\leftarrow f(i,j,k)*jf(i+1,j,t)f(i,j,k)?j

答案即為 ∑i=0Kf(n,0,i)\sum_{i=0}^K f(n,0,i)i=0K?f(n,0,i)

#include <bits/stdc++.h> using namespace std; #define int long long #define mod 1000000007 int f[2][205][1005]; int a[205]; int N, K;signed main() {scanf( "%lld %lld", &N, &K );for( int i = 1;i <= N;i ++ ) scanf( "%lld", &a[i] );sort( a + 1, a + N + 1 );f[0][0][0] = 1;for( int i = 0;i < N;i ++ ) {int o = i & 1;memset( f[o ^ 1], 0, sizeof( f[o ^ 1] ) );for( int j = 0;j <= i + 1;j ++ )for( int k = 0;k <= K;k ++ ) {int d = k + ( a[i + 1] - a[i] ) * j;if( d > K ) continue;( f[o ^ 1][j][d] += f[o][j][k] ) %= mod;( f[o ^ 1][j + 1][d] += f[o][j][k] ) %= mod;( f[o ^ 1][j][d] += f[o][j][k] * j ) %= mod;if( j ) ( f[o ^ 1][j - 1][d] += f[o][j][k] * j ) %= mod;}}int ans = 0;for( int i = 0;i <= K;i ++ ) ( ans += f[N & 1][0][i] ) %= mod;printf( "%lld\n", ans );return 0; }

[TopCoder] Seatfriends

Vjudge

與上面的AppleTrees類似。

如果把空位放入動態規劃一起考慮,計數會變得很麻煩。所以只考慮有 kkk 個位置的情況。

因為是環排列,所以先固定第一個人的位置,有 nnn 種選擇,f(1,1)=nf(1,1)=nf(1,1)=n

f(i,j):if(i,j):if(i,j):i 個人分成 jjj 組的方案數。注意保證轉移過程全程合法。

  • 在任意兩組內新增一個組:f(i+1,j+1)←f(i,j)?jf(i+1,j+1)\leftarrow f(i,j)*jf(i+1,j+1)f(i,j)?j。因為是個環,所以空應該有 jjj 個。
  • 新加一個在組的其中一邊:f(i+1,j)←f(i,j)?j?2f(i+1,j)\leftarrow f(i,j)*j*2f(i+1,j)f(i,j)?j?2
  • 合并兩個組,空仍然有 jjj 個:f(i+1,j?1)←f(i,j)?jf(i+1,j-1)\leftarrow f(i,j)*jf(i+1,j?1)f(i,j)?j

注意,當 n=kn=kn=k 的時候,直接返回 f(k?1,1)f(k-1,1)f(k?1,1)。因為最后一個人只能坐剩下來的那個空位。

最后考慮各組間插入空位的情況,對于 f(k,j)f(k,j)f(k,j) 而言,要將 n?kn-kn?k 個空位插入 jjj 個間隔中(每一個至少需要一個空位),顯然為 (n?k?1j?1)\binom{n-k-1}{j-1}(j?1n?k?1?)

ans=∑j=1Gf(k,j)(n?k?1j?1)ans=\sum_{j=1}^Gf(k,j)\binom{n-k-1}{j-1}ans=j=1G?f(k,j)(j?1n?k?1?)

#include <bits/stdc++.h> using namespace std; #define maxn 2005 #define mod 1000000007 long long f[2005][2005]; long long fac[maxn], inv[maxn];int qkpow( int x, int y ) {int ans = 1;while( y ) {if( y & 1 ) ans = 1ll * ans * x % mod;x = 1ll * x * x % mod;y >>= 1;}return ans; }void init( int n ) {fac[0] = inv[0] = 1;for( int i = 1;i <= n;i ++ ) fac[i] = fac[i - 1] * i % mod;inv[n] = qkpow( fac[n], mod - 2 );for( int i = n - 1;i;i -- ) inv[i] = inv[i + 1] * ( i + 1 ) % mod; } int C( int n, int m ) {if( n < m ) return 0;else if( ! n or ! m ) return 1;else return fac[n] * inv[m] % mod * inv[n - m] % mod; }class Seatfriends {public :int countseatnumb( int n, int k, int g ) {int N = n, K = k, G = g;init( N );f[1][1] = N;for( int i = 1;i < K;i ++ )for( int j = 1;j <= G;j ++ ) {(f[i + 1][j + 1] += f[i][j] * j) %= mod;(f[i + 1][j] += f[i][j] * j * 2) %= mod;(f[i + 1][j - 1] += f[i][j] * j) %= mod;}if( N == K ) return f[K - 1][1];int ans = 0;for( int i = 1;i <= G;i ++ ) (ans += f[K][i] * C(N - K - 1, i - 1) % mod) %= mod;return ans;} };

小結

  • 排列有空的計數,先把所有空位抽出來,最后組合數乘回去。轉移過程中假設空存在,也就是分組的依據。
  • 所求為環排列,欽定一種選法,最后乘上環大小。
  • 插入一般理解為插空
  • 注意是否區分一個組的左右。
  • 注意是否考慮組之間存在順序,如果存在順序且轉移沒考慮,記得乘組數的階乘。
  • 差分法/微分法技巧
  • 一般狀態都定義為 f(i,j,...):if(i,j,...):if(i,j,...):i 個分成 jjj 組,然后可能還要附帶一些信息。(是否抵到“墻”)
  • 無序轉有序。
  • 狀態轉移一般都有:插入自成一組;加入某一組;合并某兩組;墻組還是普通組。
  • 一般不需要什么多牛逼的優化。

總結

以上是生活随笔為你收集整理的【刷题记录】排列dp的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。