1. 程式人生 > >【做題】arc068_f-Solitaire——糊結論

【做題】arc068_f-Solitaire——糊結論

urn ons for lock clu std pac source 復雜

把所有數字放入雙端隊列後,結果大概是這樣一個排列:
\[P_1 1 P_2\]
其中\(P_1\)是遞減序列,\(P_2\)是遞增序列。

我們以\(1\)所在的位置\(k\)分割最終的排列\(A\)

其中前半部分,形象地講是由兩個遞減序列交織在一起組成的。那兩個遞減序列分別是\(P_1\)的前綴和\(P_2\)的後綴,且至少有一個就是\(P_1\)\(P_2\))本身。那麽,前半部分滿足這個性質:

可以劃分為兩個可為空的遞減子序列。

顯然這個性質難以直接判定。考慮將其轉化。對於其中編號為\(i\)的元素,設\(j\)\(i\)後面的第一個與\(A_i\)屬於不同子序列的元素位置。我們不妨分類討論一發:

  • \(A_i > A_j\),因為兩個子序列遞減,有\(\forall j \in [i+1,k-1], \, A_i>A_j\)
  • \(A_i < A_j\),因為兩個子序列遞減,有\(\forall j \in [1,i-1], \, A_i<A_j\)

因此,我們得出:

長度為\(n\)的最終排列\(A\)的前半部分可以劃分為兩個可為空的遞減子序列$\implies $
$ \forall i \in [1,k-1]$,下列條件至少滿足一個:

  • \(\forall j \in [i+1,k-1], \, A_i>A_j\),記為條件1
  • \(\forall j \in [1,i-1], \, A_i<A_j\)
    ,記為條件2

我們發現,逆命題也是成立的,因為最長不上升子序列的長度小於等於2。(導彈攔截可經典了)於是我們就獲得了一個判斷方法。
但還有一個問題,即從\(1\)\(n\)的排列中任取出兩個元素不重復的遞減序列\(D_1\),\(D_2\),它們組成的序列\(P\),不一定可以作為\(A\)的前半部分。我們還要保證\(D_1\)的最後一個元素和\(D_2\)的最後一個元素中的較大值大於後半部分的所有元素。依然要對此進行轉化。
那麽,對於我們已知的一個\(A\)的前半部分,我們嘗試將其劃分且最大化兩個子序列最後一個元素的較大值,設它的位置是\(p\)。顯然\(p\)後面沒有比\(A_p\)

更大的元素,否則能得到更優解。通過簡單分類討論,我們發現所有不滿足條件2的元素都大於後半部分的所有元素。因此我們可以直接修改條件2:
\[\forall j \in [i+1,n], \, A_i > A_j\]
故我們得到了前半部分的判斷方法。

後半部分的判斷就簡單多了。它是一個遞減序列不斷從前面或後面取出元素得到的,即每次取出的元素都是剩下的元素中最大(最小)的。容易得到且證明它合法的充要條件:

$ \forall i \in [k+1,n]$,下列條件至少滿足一個:

  • \(\forall j \in [i+1,n], \, A_i > A_j\)
  • \(\forall j \in [i+1,n], \, A_i < A_j\)

我們終於簡化了題意:

求有多少個長度為\(n\)的排列\(A\)滿足:

  • \(A_k = 1\)
  • \(\forall i \in [1,k)\)\(A_i\)小於它前面的所有數或大於它後面的所有數。
  • \(\forall i \in (k,n]\)\(A_i\)小於它後面的所有數或大於它後面的所有數。

剩下的就簡單了。由遞推式可知,後半部分的方案數就是\(2^{n-k-1}\)。而對於前半部分,我們設dp狀態\(i,j\)表示當前有\(i\)個元素,最小的滿足條件2的元素是其中第\(j\)個(不存在則為\(j+1\)),最終答案就是
\[2^{n-k-1} \sum_{i=1}^{k} {{i-1+n-k}\choose{i-1}} dp_{k-1,i}\]
時間復雜度\(O(n^2)\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2010, MOD = 1e9 + 7;
int dp[2][N],n,k,s,p,sum[N],ans,jc[N],inv[N];
typedef long long ll;
ll power(ll a,int b) {
  ll res = 1;
  while (b) {
    if (b&1) (res *= a) %= MOD;
    (a *= a) %= MOD;
    b >>= 1;
  }
  return res;
}
ll comb(int a,int b) {
  if (a < 0 || b < 0 || a < b) return 0;
  return 1ll * jc[a] * inv[b] % MOD * inv[a-b] % MOD;
}
signed main() {
  scanf("%lld%lld",&n,&k);
  jc[0] = 1;
  for (int i = 1 ; i <= n ; ++ i)
    jc[i] = 1ll * jc[i-1] * i % MOD;
  inv[n] = power(jc[n],MOD-2);
  for (int i = n-1 ; i >= 0 ; -- i)
    inv[i] = 1ll * inv[i+1] * (i+1) % MOD;
  s = k-1;
  p = 0;
  sum[2] = 1;
  sum[3] = -1;
  for (int i = 1 ; i <= s ; ++ i) {
    p ^= 1;
    memset(dp[p],0,sizeof dp[p]);
    for (int j = 1 ; j <= i+1 ; ++ j)
      (sum[j] += sum[j-1]) %= MOD;
    for (int j = 1 ; j <= i+1 ; ++ j)
      (dp[p][j] += sum[j]) %= MOD, sum[j] = 0;
    sum[i+2] = 0;
    for (int j = 1 ; j <= i+1 ; ++ j) {
      (sum[2] += dp[p][j]) %= MOD;
      (sum[j+2] -= dp[p][j]) %= MOD;
    }
  }
  for (int i = 1 ; i <= s + 1 ; ++ i) {
    (ans += 1ll * comb(n-k+i-1,i-1) * dp[p][i] % MOD);
    ans %= MOD;
  }
  if (k == 1) ans = 1;
  if (n > k)
    ans = 1ll * power(2,n-k-1) * ans % MOD;
  ans = (ans + MOD) % MOD;
  printf("%lld\n",ans);
  return 0;
}


小結:推導結論實在是一個困難的過程,積極的心態和清晰的思路是不可或缺的。

【做題】arc068_f-Solitaire——糊結論