AtCoder ABC217F Make Pair

ABC217F - Make Pair

tag: 区间 DP,计数

\(2N\) 名学生排成一列,从左到右编号分别为 \(1,2,\cdots,2N\),其中已知 \(M\) 对学生 \(A_i,B_i\)\(1\le i\le M\))为好友。

老师将进行以下操作 \(N\) 次,最终形成 \(N\) 对学生组合:

  • 每次选择 相邻且互为好友 的两名学生,将他们配对并从队列中移除;
  • 若被移除的学生不在队列两端,则左右两侧的学生会靠拢成为新的相邻关系。

求完成 \(N\) 次操作的可能方案数,结果对 \(998244353\) 取模。

\(1\le N\le200\)。保证 \(0\le M\le N(2N-1)\)\(1\le A_i,B_i\le2N\),且所有 \(\{A_i,B_i\}\) 组合均不相同。

注意到移除的过程一定是从里向外扩张。考虑区间 DP,设 \(dp(l,r)\) 表示将 \([l,r]\)​ 范围内的学生移除的方案数。

初始状态:若 \(i,i+1\) 为好友,则 \(dp(i,i+1)=1\)

另外,显然 \(dp(l,r)>0\) 仅当 \(r-l+1\) 为偶数。

\(dp(l,r)\) 可能由两种情况转移而来:

  • \(l,r\) 互为好友,则最后移除 \(l,r\),贡献为 \(dp(l+1,r-1)\)
  • 从中间断开,枚举断点 \(k\)\(l+1\le k\le r-1\)),分成两部分 \([l,k]\)\([k+1,r]\),若不考虑移除的顺序,方案数首先为 \(dp(l,k)\cdot dp(k+1,r)\),再考虑到每次可以选择在左侧移除或者在右侧移除,其中左侧共移除 \((k-l+1)/2\) 次,右侧共移除 \((r-k)/2\) 次,两侧共移除 \((r-l+1)/2\) 次,因此再乘上一个 \(\binom{(r-l+1)/2}{(r-k)/2}\)

根据加法原理,这两种情况再相加即可。

然而我们发现,如果单纯这样转移,可能有重复的情况,因为不同断点的方案可能是相同的。

例如 \(1,2,3,4,5,6\) 相邻的两两为朋友,\(dp(1,6)\) 可能被分成 \(dp(1,2),dp(3,6)\)\(dp(1,4),dp(5,6)\),而 \(dp(3,6)\) 内部和 \(dp(1,4)\) 内部再分成两部分,就导致了方案 \((1,2),(3,4),(5,6)\) 被计算了两次。

考虑类似《进阶指南》中 P10956 金字塔 的做法,以 第一个 极大的区间长度(\(k\))作为阶段进行转移,这样就保证了不重不漏。

综上,状态转移方程为:

\[dp(l,r)=dp(l+1,k-1)\cdot dp(k+1,r)\cdot\binom{\frac{r-l+1}2}{\frac{r-k}2}, \]

其中 \(l,k\) 互为朋友,\(l+1\le k\le r\),且 \(k\)\(l\) 奇偶性不同。

注意:\(k=l+1\) 时以 \([l,l+1]\) 为第一个极大区间,\(dp(l+1,l)\) 贡献为 \(1\)\(k=r\) 时以 \([l,r]\) 为唯一的极大区间,\(dp(r+1,r)\) 贡献也为 \(1\)。因此需要定义 \(dp(i+1,i)=1\)

#include <bits/stdc++.h>
using namespace std;
int const MOD = 998244353;
int const N = 410;
int dp[N][N], C[N][N];
bool f[N][N];

int ksm(int a, int x) {
    int res = 1;
    while (x) {
        if (x & 1) res = res * 1ll * a % MOD;
        a = a * 1ll * a % MOD;
        x >>= 1;
    }
    return res;
}

void solve() {
    int n, m;
    cin >> n >> m;
    C[0][0] = 1;
    for (int i = 1; i <= (n << 1); i++) {
        dp[i + 1][i] = 1;
        C[i][0] = 1;
        for (int j = 1; j <= i; j++) {
            C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
        }
    }
    for (int i = 1, a, b; i <= m; i++) {
        cin >> a >> b;
        f[a][b] = true;
        if (a == b - 1) {
            dp[a][b] = 1;
        }
    }
    for (int d = 4; d <= (n << 1); d += 2) {
        for (int l = 1, r = d; r <= (n << 1); l++, r++) {
            for (int k = l + 1; k <= r; k += 2) {
                if (!f[l][k]) continue;
                dp[l][r] += 1ll * dp[l + 1][k - 1] * dp[k + 1][r] % MOD * C[d / 2][(r - k) / 2] % MOD;
                dp[l][r] %= MOD;
            }
        }
    }
    cout << dp[1][n << 1] << '\n';
    return;
}

signed main() {
    cin.tie(0)->sync_with_stdio(false);

    int tt = 1;
    // cin >> tt;
    while (tt--) solve();

    return 0;
}
posted @ 2025-07-16 11:23  f2021ljh  阅读(17)  评论(0)    收藏  举报