LOJ3405 「2020-2021 集训队作业」Gem Island 2
组合计数神题。下文的 指原题面中的 , 指原题面中的 。
考虑最后每个人得到的宝石数量的序列 ,考虑这种方案的出现次数。首先要在 次操作中分别选 次给第 个人,方案数为 ;然后对于一个人,当它的宝石数为 时,选择一颗变成两颗的方案数为 ,所以每个人还有 种方案的贡献,乘起来发现 被约掉了,也就是说对于任意一种 ,它们出现概率均等。
于是题目变成了,一个序列的价值为前 大元素之和,求所有满足 的序列 的价值之和(最后要除个方案数 和加上原有的 个)。
考虑对于一个固定的序列 ,我们以这样的方式计算它的价值:。可以想象成一个柱状图,就是把原来的从左到右算面积变成从上到下算面积。这样可以方便许多。
那么设 为对于一个序列 ,恰好有 个元素的值 的这样的序列个数;考虑二项式反演,设 为对于一个序列 ,钦定有 个元素的值 的这样的序列个数。 是好求的,相当于选 个位置让他们 ,就变成了一个和为 的插板,那么:
且:
反演得:
答案即为:
这样已经可以做 了,但是还不够。考虑把 扔掉,直接计算每个 对答案的贡献,那么答案也可以表示成:
后面那一坨求和式子看上去不能考虑组合意义,那么可以尝试递推。设 ,那么根据 可以裂项后尝试递推:
那么我们得到了 ,边界 。考虑组合意义,可以看作是, 次选 次让 , 次让 不 并变号,那么有 。再尝试递推:
最后一步是运用了正负项抵消。那么我们有 。
再来考虑答案的式子:
分别代入 的式子得:
关注后半部分:
注意到 ,所以暴力算的复杂度就是 的。但是还能更优。
其中 ,可以高维前缀和预处理出。
至此,我们终于以 的复杂度完成了这题。
code
// Problem: #3405. 「2020-2021 集训队作业」Gem Island 2 // Contest: LibreOJ // URL: https://loj.ac/p/3405 // Memory Limit: 512 MB // Time Limit: 1200 ms // // Powered by CP Editor (https://cpeditor.org) #include <bits/stdc++.h> #define pb emplace_back #define fst first #define scd second #define mkp make_pair #define mems(a, x) memset((a), (x), sizeof(a)) using namespace std; typedef long long ll; typedef double db; typedef unsigned long long ull; typedef long double ldb; typedef pair<ll, ll> pii; const int maxn = 30000100; const ll mod = 998244353; inline ll qpow(ll b, ll p) { ll res = 1; while (p) { if (p & 1) { res = res * b % mod; } b = b * b % mod; p >>= 1; } return res; } int n, m, K, N, fac[maxn], ifac[maxn], f[maxn], pr[maxn / 10], tot; bool vis[maxn]; inline ll C(int n, int m) { if (n < m || n < 0 || m < 0) { return 0; } else { return 1LL * fac[n] * ifac[m] % mod * ifac[n - m] % mod; } } void solve() { scanf("%d%d%d", &n, &m, &K); N = n + m; fac[0] = 1; for (int i = 1; i <= N; ++i) { fac[i] = 1LL * fac[i - 1] * i % mod; } ifac[N] = qpow(fac[N], mod - 2); for (int i = N - 1; ~i; --i) { ifac[i] = 1LL * ifac[i + 1] * (i + 1) % mod; } for (int i = 2; i <= m; ++i) { if (!vis[i]) { pr[++tot] = i; } for (int j = 1; j <= tot && i * pr[j] <= m; ++j) { vis[i * pr[j]] = 1; if (i % pr[j] == 0) { break; } } } for (int i = 1; i <= m; ++i) { f[i] = C(n + m - i - 1, n - 1); } for (int i = 1; i <= tot; ++i) { for (int j = m / pr[i], k = m / pr[i] * pr[i]; j; --j, k -= pr[i]) { int &t = f[j]; t = (((t += f[k]) >= mod) ? t - mod : t); } } ll ans = 1LL * n * f[1] % mod; for (int i = 2; i <= n; ++i) { ans = (ans + C(n, i) * (((i - K) & 1) ? mod - 1 : 1) % mod * C(i - 2, K - 1) % mod * f[i]) % mod; } ans = ans * qpow(C(n + m - 1, n - 1), mod - 2) % mod; ans = (ans + K) % mod; printf("%lld\n", ans); } int main() { int T = 1; // scanf("%d", &T); while (T--) { solve(); } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理