Atcoder nomura2020F Sorting Game

rizynvu發表於2024-08-13

首先考慮如果固定了 \(a\),如何判定這個 \(a\) 是否能被排序。
如果存在 \(a_i > a_j(i < j)\),那麼 \(a_i\) 肯定要交換到 \(a_j\) 後面,那麼就肯定會交換 \(a_i, a_j\)

於是合法條件就是如果存在 \(a_i > a_j(i < j)\),那麼 \(a_i, a_j\) 只相差一個二進位制位。
那就還能知道此時一定 \(a_i\) 這一位為 \(1\)\(a_j\) 這一位為 \(0\),其餘位都相同。

於是可以考慮從高位開始往低位比較兩個數字。
如果滿足 \(a_i, a_j(i < j)\) 前面已經遍歷的位都相同,這一位不同且 \(a_i\) 這一位為 \(1\)\(a_j\)\(0\),就要求剩下的位 \(a_i, a_j\) 相同。

於是可以考慮 DP。
考慮到如果有了上面提到的情況,那麼此時 \(a_i, a_j\) 在低位就應該當作一個數了,相當於合併操作,同時還有提到從高位往低位去比較。 就可以設狀態 \(f_{n, m}\) 為最高位為 \(n\),當前有 \(m\) 個數的方案數。

接下來考慮轉移,分為兩種情況考慮:

  1. 不存在合併操作。
    那麼這個時候對於 \(n\) 這一位,就應該滿足前面一部分為 \(0\),後面這部分為 \(1\),一共會有 \(m + 1\) 種可能的 \(01\) 分界線。

    同時考慮到此時先手可以操作使得這一位全變為 \(0\),那麼比較還得繼續到下一位。

    有轉移 \(f_{n, m} = (m + 1)f_{n - 1, m}\)

  2. 存在合併操作,即存在 \(i < j\)\(a_i\)\(n\) 位為 \(1\)\(a_j\)\(n\) 位為 \(0\)

    考慮找到最靠前的為 \(1\) 的位置 \(l\),最靠後的為 \(0\) 的位置 \(r\),此時肯定滿足 \(l < r\)
    那麼可以知道的是,不論 \(l < i < r\) 裡的 \(i\)\(i\) 位是什麼,肯定都會和 \(l\) 合併或者和 \(r\) 合併。
    於是可以知道,\(l\le i\le r\)\(a_i\) 都會合並,還剩下 \(m - (r - l)\) 個數。

    同時如果先手也選擇了把這一位設成 \(0\),還需要輪到下一輪比較。

    此時的方案數為中間 \(r - l - 1\) 個數這一位任選的 \(2^{r - l - 1}\)

    進一步的,考慮到對於同樣的 \(r - l\),不管 \(l, r\) 是多少,本質都是相同的,此時對應的有 \(m - (r - l)\) 種方案。

    於是考慮列舉 \(m - (r - l)\),有轉移 \(f_{n, m} = \sum\limits_{i = 1}^{m - 1} i2^{m - i - 1} f_{n - 1, i}\)

綜上,有轉移 \(f_{n, m} = (m + 1)f_{n - 1, m} + \sum\limits_{i = 1}^{m - 1} i2^{m - i - 1} f_{n - 1, i}\)

對於轉移的最佳化,考慮拎出後面求和的部分,令 \(g_{n, m} = \sum\limits_{i = 1}^{m - 1} i2^{m - i - 1} f_{n - 1, i}\)
那麼有 \(g_{n, m + 1} = \sum\limits_{i = 1}^m i 2^{m - i} f_{n - 1, i}\)
可以發現有 \(g_{n, m + 1} = 2g_{n, m} + mf_{n - 1, m}\),可以遞推求出。

時間複雜度 \(\mathcal{O}(nm)\)

#include<bits/stdc++.h>
using ll = long long;
constexpr ll mod = 1e9 + 7;
const int maxn = 5e3 + 10;
ll f[maxn], g[maxn];
int main() {
   int n, m; scanf("%d%d", &n, &m);
   std::fill(f + 1, f + m + 1, 1);
   while (n--) {
      std::copy(f + 1, f + m + 1, g + 1);
      for (int i = 1; i <= m; i++)
         f[i] = (f[i - 1] * 2 + g[i - 1] * (i - 1)) % mod;
      for (int i = 1; i <= m; i++)
         (f[i] += g[i] * (i + 1)) %= mod;
   }
   printf("%lld\n", f[m]);
   return 0;
}

相關文章