atcoder.F-Make Pair(区间DP&括号匹配&排列组合&去重)

总结收获

题目

  1. 传送门
  2. 题意
    1. N表示人的个数为2*N,M表示不同的好朋友的对数。(N<=200)
    2. 最开始2N个人排列成1,2,…,2N,每次操作可以去掉一对相邻的好朋友,中间空隙补上,求能把所有人都去掉的次数总和。
    3. 两次不同:存在 1 ≤ i ≤ N 1\le i\le N 1iN,使得这两次在第 i 次操作去掉两个人不完全相同。
      在这里插入图片描述
  3. 题解:首先dp[i][j]表示取完区间i~j所有的数的不同操作顺序种数。
    1. 很像括号匹配,所以就是区间DP,然后现在考虑怎么转移;
    2. 在求区间[i,j]的时候,所有小的区间的dp值都已经知道了,又要去重,又要正确转移,先考虑怎样不重复的枚举,然后再考虑每一步怎样转移:
      1. 枚举和i匹配的数,首先i~i+1或者i~j+1的时候很好操作,考虑其他i~k匹配。
      2. 两个区间,取数顺序一定,那么排列种数为C(len1+len2,len1)即在len1+len2中取len1个位置给前面的区间,然后乘上他们的顺序种数即可。
  4. 代码
#include <bits/stdc++.h>
#define int long long
#define ll long long
#define pii pair<int, int>
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int N = 1e3 + 10;
const int mod = 998244353;

int n, m;
int a, b;
int dp[N][N], mp[N][N];
// return len(l,r)/2;
int Len(int l, int r) { return (r - l + 1) / 2; }
int re[N], inv[N], fac[N];
void init(int n) {
    re[0] = inv[1] = fac[0] = re[1] = fac[1] = 1;
    for (int i = 2; i <= n; ++i) {
        fac[i] = fac[i - 1] * i % mod;
        inv[i] = (mod - mod / i) * inv[mod % i] % mod;
        re[i] = re[i - 1] * inv[i] % mod;
    }
}
int C(int a, int b) { return fac[a] * re[b] % mod * re[a - b] % mod; }
signed main() {
    cin >> n >> m;
    init(2 * n);
    for (int i = 1; i <= m; i++) {
        scanf("%lld%lld", &a, &b);
        if (a > b) swap(a, b);
        mp[a][b] = 1;
    }
    for (int len = 2; len <= 2 * n; len += 2) {
        // dbg(len);
        for (int i = 1; i <= 2 * n; i++) {
            int j = i + len - 1;
            if (j > 2 * n) break;
            if (len == 2)
                dp[i][j] = mp[i][j];
            else {
                // i~i+1
                if (mp[i][i + 1] && dp[i + 2][j])
                    dp[i][j] =
                        (dp[i][j] + (Len(i + 2, j) + 1) * dp[i + 2][j] % mod) %
                        mod;
                //这中间,怎么算?在len1+len2中选len1个数给前面区间,剩下的就是后面的,然后一种特定顺序就有C(len1+len2,len1)种可能
                //然后,又有dp[i+1][k-1]*dp[k+1][j]种可能顺序
                for (int k = i + 3; k <= j - 2; k += 2) {
                    if (mp[i][k] && dp[i + 1][k - 1] && dp[k + 1][j]) {
                        dp[i][j] =
                            (dp[i][j] + C(Len(i + 1, k - 1) + 1 + Len(k + 1, j),
                                          Len(k + 1, j)) *
                                            dp[i + 1][k - 1] % mod *
                                            dp[k + 1][j] % mod) %
                            mod;
                    }
                }
                // i~j
                if (mp[i][j] && dp[i + 1][j - 1])
                    dp[i][j] = (dp[i][j] + dp[i + 1][j - 1] % mod) % mod;
            }
        }
    }

    cout << dp[1][2 * n] << endl;
    return 0;
}
/*
input:::
2 3
1 2
1 4
2 3
output:::
1
input:::
2 2
1 2
3 4
output:::
2
input:::
2 2
1 3
2 4
output:::
0
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值