某岛

… : "…アッカリ~ン . .. . " .. .
November 5, 2022

Codeforces Round #831

题意

给你一个含有 $n$ 个元素的可重集合 $A$,集合 $A$ 的每个元素是一个单元素集合 ${a_i}$。
定义一次操作由以下两步构成:

  • 选择 $A$ 中两个交集为空的集合 $S$, $T$。
  • 删除这两个集合,将它们的并添加进 $A$。

之后, 我们构造一个可重集合 $M$,表示当前剩下的所有集合的大小。
你可以执行任意多次操作,问所有不同的 $M$ 的总数。

做法

如果初始的元素互不相同,显然原问题等价于分拆数,虽然原问题是以合并的方式给出的。
那么原问题等价于带有某种限制的分拆数,限制为某些元素之间必须分拆。

分拆数我们知道有很多高级做法。。。比如欧拉五边形数、生成函数云云。。。。但是这个题 $n$ 只有 2000,大概率都用不到。

考虑分拆数最朴素的 DP。。。我们把分拆的结果按照从大到小排序。。那么每一种情况唯一对应一个单调递减且和为 n 的序列。
dp[i][s][j]: 当前考察前 i 个位置,和为 s 且末尾为 j 的方案数。
有:

const int N = int(1e3) + 9;
int dp[2][N][N];
int n;

int main() {
#ifndef ONLINE_JUDGE
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
#endif
    RD(n);

    int p = 0, q = 1; RST(dp[p]); dp[p][0][n] = 1;

    REP_1(i, n) {
        swap(p, q); RST(dp[p]);
#define u dp[q][s][j]
#define v dp[p][s+k][k]
        REP(s, n+1) REP(j, n+1) if (u) {
            DWN_1(k, min(j, n-s), 0) v += u;
        }
        cout << dp[p][i][0] + 1 << " ";
    }
    cout << endl;
}

这个 DP 可以用部分和优化到 $O(n^3)$。
dp[i][s][j]: 当前考察前 i 个位置,和为 s 且末尾 >= j 的方案数。

const int N = int(1e3) + 9;
int dp[2][N][N];
int n;

int main() {
#ifndef ONLINE_JUDGE
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
#endif
    RD(n);

    int p = 0, q = 1; RST(dp[p]); dp[p][0][n] = 1;

    REP_1(i, n) {
        swap(p, q); RST(dp[p]);
#define u dp[q][s][j]
#define v dp[p][s+j][j]
        REP(s, n+1) DWN(j, n+1, 0) u += dp[q][s][j+1];
        REP(s, n+1) REP(j, n+1) if (u) v += u;

        cout << dp[p][i][0] + 1 << " ";
    }
    cout << endl;
}

继续考察限制。。。结论是。。。它等价于 $s \leq \sum \min(i, c)$
其中 $c$ 遍历过初始集合中的每个元素出现的次数。。。

这个结论真的不是很显然。。。感觉需要用共轭的性质 + 各种归纳 yy 出来。。。
于是加上上面的限制作为剪枝就可以 O(n2logn) 跑出来。。。

但是我们知道其实分拆数的朴素 dp 可以进一步优化到 $O(n2)$。。。
原因是分差数本身就更特殊。。。状态可以进一步简化。。。