[BZOJ4665] 小 w 的喜糖

思路

坏了这次没啥思路

转化题意, 求存在多少种数列 B , 使得 BA 中, 每种元素出现的次数相同并且满足 AiBi
是这样转化的吗

你考虑直接算, 但是这样无论如何你要记录每种元素当前的出现次数作为状态, 不可能啊
怎么做比较方便? 看下标签发现可以使用「二项式反演」

你考虑到「钦定」k 个位置出现的元素不同不好处理, 考虑常见转化, 转化成「钦定」k 个位置出现的元素相同

「钦定」nk 个元素相同的方案数我们记为 f(k)
考虑 f(k) 的计算, 显然需要对于每种颜色处理
nk 个元素分给 m 种颜色让人联想到背包问题, 那么我们考虑 dp 去做
具体的, 你「钦定」nk 个元素相同, 其他的随意排列即可

dpi,j 表示考虑前 i 种颜色,「钦定」j 个位置与原序列相同 (序列初始顺序与答案无关, 视作按颜色排序) 时对于相同部分可能的方案数, 记 ci 表示颜色 i 的元素个数

有转移

dpi,j=k=0min(ci,j)dpi1,jk(cik)

然后你发现剩下的情况是多重集的排列, 所以容易发现

f(k)=dpm,nkk!(ciki)!

这个 dp 时处理即可

常见套路

f(k)=i=0k(nink)g(i)g(k)=i=0k(1)ki(nink)f(i)

代码

#include <bits/stdc++.h>
using namespace std;

#define QwQ01AwA return 0
#define ll long long
#define look_time cerr << 1.0 * clock() / CLOCKS_PER_SEC << '\n'
template <typename T> void ckmax(T &x, T y) {x = max(x, y);}
template <typename T> void ckmin(T &x, T y) {x = min(x, y);}

const int N = 2005;
const int mod = 1e9 + 9;
int ksm(int a, int b) {
    int ans = 1;
    for (; b; b >>= 1, a = 1ll * a * a % mod) {
        if (b & 1) ans = 1ll * ans * a % mod;
    }
    return ans;
}
int n;
int siz[N], f[N], g[N];
int fac[N], inv[N];

void init(int n) {
    fac[0] = 1;
    for (int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % mod;
    inv[n] = ksm(fac[n], mod - 2);
    for (int i = n; i >= 1; i--) inv[i - 1] = 1ll * inv[i] * i % mod;
}
int C(int n, int m) {
    return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    init(2000);
    cin >> n;
    for (int i = 1, x; i <= n; i++) cin >> x, siz[x]++;
    f[0] = 1;
    for (int i = 1; i <= n; i++) {
        if (!siz[i]) continue;
        memset(g, 0, sizeof(g));
        for (int j = 0; j <= n; j++) {
            if (!f[j]) continue;
            for (int k = 0; k <= siz[i]; k++) {
                g[j + k] = (g[j + k] + 1ll * C(siz[i], k) % mod * inv[siz[i] - k] % mod * f[j] % mod) % mod;
                g[j + k] = (g[j + k] % mod + mod) % mod;
            }
        }
        memcpy(f, g, sizeof(f));
    }
    int ans = 0;
    for (int i = 0; i <= n; i++) ans = (ans + ((n - i) % 2 ? -1 : 1) % mod * 1ll * f[n - i] % mod * fac[i] % mod) % mod;
    cout << (ans + mod) % mod << '\n';
    QwQ01AwA;
}

总结

dpf 其实挺常见的

首先发现只能处理相同元素, 需要分配
背包处理分配问题是常见的

「钦定」意义下, 「至多」和「恰好」的转化

第一次见到组合数学中「多重集的排列」

posted @   Yorg  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示