连续段DP && TopCoder12909 Seatfriends 题解

前置知识:组合数

题目链接(VJudge):TopCoder12909 Seatfriends

有一张圆桌,周围 \(n\)有编号的座位。依次来了 \(k\) 个人,每个人也有编号,可以坐在任意一个空位上,但是要满足:每个人坐下来以后,形成的人的连通块个数不大于 \(m\)。答案对 \(10^9 + 7\) 取模。
\(1 \le m \le k \le n \le 2000\)

考虑 dp。初步想法是模拟整个过程,但是发现要记录的东西比较多。

于是可以考虑简化状态,设 \(f_{i,j}\) 表示来了 \(i\) 个人,连通块个数为 \(j\) 的方案数。由于是个环,所以钦定第一个人所在连通块为第一个连通块。

如果第 \(i+1\) 个人不与之前的任何人相邻,即单独一个连通块。则可以插到 \(j\) 个空中,即

\[dp_{i+1,j+1} \gets dp_{i,j} \times j \]

如果第 \(i+1\) 个人接在当前的某一个连通块的左边/右边(即出现在连通块两端),连通块数量不变。则有 \(j\) 个连通块、两端可以插,即

\[dp_{i+1,j} \gets dp_{i,j} \times 2j \]

如果某两个连通块以第 \(i+1\) 个人为介质合并(即此人出现在连通块中间),连通块数量少一。则可以插到 \(j\) 个空中,即

\[dp_{i+1,j-1} \gets dp_{i,j} \times j \]

那么具体把连通块安排在哪个位置,在最后的答案用组合数统计即可

\[ans = \sum_{i=1}^{m} dp_{k,i}\times {n-k-1 \choose i-1} \]

此外,还有一种特殊情况 \(n=k\),因为不难发现此时答案无法正常统计。那么其方案数就为 $ dp_{k-1,1}\times n $。(注意并不是 \(dp_{k,1}\) 也不是 \(k!\),因为当 \(m=1\) 时,前者只会考虑最后一个人在左右两端的情况,而后者显然会有连通块个数 \(>1\) 的时刻。)

这种类型的 dp 题被称为「连续段 dp」,主要思想是 dp 过程中只关心连通块的相对顺序,以及每个块内的顺序(而不关心连通块的位置)。

代码:(仅供参考)

(没法交到 OJ 上所以只是把样例过了,并不保证能 AC,但大概是没问题的。有问题的话欢迎评论或私信。另外 OJ 上要用 class 的。)

点击查看代码
// Author: AquariusZhao
#include <bits/stdc++.h>
using namespace std;
const int N = 2005, MOD = 1e9 + 7;
int n, m, k, dp[N][N], C[N][N], ans;

inline int madd(int x, int y) { return x + y >= MOD ? x + y - MOD : x + y; }
inline int mmul(int x, int y) { return 1ll * x * y % MOD; }
inline void add(int &to, int from)
{
    to = madd(to, from);
}

int main()
{
#ifdef aquazhao
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
#endif
    cin >> n >> k >> m;
    dp[1][1] = 1;
    for (int i = 1; i < k; i++)
        for (int j = 1; j <= m; j++)
        {
            // 新建一个组
            add(dp[i + 1][j + 1], mmul(dp[i][j], j));
            // 接在某一个组的左边/右边
            add(dp[i + 1][j], mmul(dp[i][j], j * 2));
            // 接在两个组中间,合并成一个
            add(dp[i + 1][j - 1], mmul(dp[i][j], j));
        }
    if (n == k)
    {
        cout << mmul(dp[k - 1][1], n) << endl;
        return 0;
    }
    C[0][0] = 1;
    for (int i = 1; i <= n; i++)
    {
        C[i][0] = 1;
        for (int j = 1; j <= i; j++)
            C[i][j] = madd(C[i - 1][j], C[i - 1][j - 1]);
    }
    for (int i = 1; i <= m; i++)
    {
        add(ans, mmul(dp[k][i], mmul(n, C[n - k - 1][i - 1])));
        printf("%d%c", dp[k][i], " \n"[i == m]);
    }
    cout << ans << endl;
    return 0;
}

一道练习:P2612 [ZJOI2012] 波浪

题解戳这

posted @ 2024-11-13 09:58  Aquizahv  阅读(84)  评论(0)    收藏  举报