Kth MIN-MAX 反演

MIN-MAX 反演

我们知道对于普通的 minmax 容斥有如下式子:

max(S)=TS(1)|T|+1min(T)min(S)=TS(1)|T|+1max(T)

证明可以构造一一映射,抵消贡献。

上述式子在期望意义下也是成立的:

E[max(S)]=TS(1)|T|+1E[min(T)]

证明可以考虑期望的线性性。


Kth MIN-MAX 反演

本文参考了 张俊逸同学 的集训讲解。

构造容斥系数

我们可以推广到求第 k 大元素上来。

我们尝试构造容斥系数 f ,满足:

kthmax(S)=TSf|T|min(T)

然后来考虑一下对于集合 T 中第 i 大的元素,如果 min(T) 等于这个元素,那么只有比它大的 i1 个元素是可能存在的,然后我们考虑 T 什么时候才能对于答案进行贡献,其实就是要满足 [i=k]

那么表达出来就是

j=1i1(i1j)fj+1=[i=k]

二项式反演

这个形式我们可以套用二项式反演(广义容斥定理)

原来我博客提及的都是从 i=kn 的,其实 i=1k 也是一样的。

二项式反演:

假设数列 fg 满足:

gi=j=1i(ij)fj

那么就有

fi=j=1i(1)ij(ij)gj

可以考虑生成函数证明,设 f0=g0=0

前者就为 G=Fex 后者为 F=Gex ,显然是等价的。

那么对于前面那个式子,我们设 gi1=[i=k],fj=fj+1

那么根据二项式反演就可以得到

fi=j=0i(1)ij(ij)gj

fi+1=(1)i(k1)(ik1)

随意整理一下就有

fi=(1)ik(i1k1)

结论

最终我们就可以如下求 kthmax

kthmax(S)=TS(1)|T|k(|T|1k1)min(T)

不难发现只有元素个数 k 的子集是有用的。

例题

Luogu P4707 重返现世

题意

给定集合 S 中每个元素出现的概率 pim ,其和为1 ,每次会按概率出现,每次会按概率出现一个元素。

求出现 k 个元素的期望次数。

|S|=n 满足限制

n1000,|nk|10,1p=m10000

题解

出现 k 个元素的期望次数等价于出现时间第 nk+1 晚的数出现的期望次数。

那么套用 kthmax 容斥后,我们相当于要求每个集合出现最早的数的期望次数 E(T)

参考 此处 就可以得到

E(T)=1iTpim

整理一下

E(T)=miTpi

如果 |S|20 那么就可以直接做了,可是这题数据范围有点大,不太好做。

但是我们发现 m 不大,那么我们可以对于每个 iTpi 求得其系数解决。

gi,j 为集合大小为 i ,概率和为 j 的方案数,但是这样的话直接 dp 复杂度是 O(nmk) 的,我们显然不能这样做。

我们需要把容斥系数也要考虑到一起 dp

fi,j 表示当前选定的集合的 p 的和为 i ,组合数下面那里 k=j

对于当前物品而言,只有两种选择:要么加入集合,要么不加入。

  • 不加入,直接转上去 fi,jfi,j

  • 加入这个元素,考虑其贡献。

    f 的形式为 fjp,k1=i(1)i(k1)(i1k2)gi,jp

    那么就会导致集合大小强制多 1 ,并且概率和变成 j ,那么就是 i(1)ik(ik1)gi,jp

    接下来 强行 凑组合数递推形式 (nm)=(n1m)+(n1m1) ,把 (ik1) 变为 (ik)

    那么我们要减去的其实就是 (1)ik(i1k1)gi,jp ,其实就是 fjp,k

那么最后的转移就是 fi,j+fip,j1fip,jfi,j

注意要滚动第一维,不然开不下。

复杂度是 O(n(nk)m) ,还是很暴力。

后面这些强行套的地方好没有意思啊!

代码

#include <bits/stdc++.h> #define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; } template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("P4707.in", "r", stdin); freopen ("P4707.out", "w", stdout); #endif } const int N = 1010, Mod = 998244353; int n, K, m, dp[2][10010][13]; inline int fpm(int x, int power) { int res = 1; for (; power; power >>= 1, x = 1ll * x * x % Mod) if (power & 1) res = 1ll * res * x % Mod; return res; } inline void Add(int &x, int y) { if ((x += y) >= Mod) x -= Mod; } int main () { File(); n = read(); K = n - read() + 1; m = read(); For (i, 1, K) dp[0][0][i] = Mod - 1; int cur = 0; For (i, 1, n) { int p = read(); For (j, 0, m) For (k, 0, K) if (dp[cur][j][k]) { Add(dp[cur ^ 1][j][k], dp[cur][j][k]); Add(dp[cur ^ 1][j + p][k + 1], dp[cur][j][k]); Add(dp[cur ^ 1][j + p][k], Mod - dp[cur][j][k]); dp[cur][j][k] = 0; } cur ^= 1; } int ans = 0; For (i, 1, m) ans = (ans + 1ll * m * fpm(i, Mod - 2) % Mod * dp[cur][i][K]) % Mod; printf ("%d\n", ans); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10252459.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(1035)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示