• Kth MIN-MAX 反演


    MIN-MAX 反演

    我们知道对于普通的 (min-max) 容斥有如下式子:

    [max(S) = sum_{T subseteq S} (-1)^{|T| + 1} min(T)\ min(S) = sum_{T subseteq S} (-1)^{|T| + 1} max(T) ]

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

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

    [E[max(S)] = sum_{T subseteq S} (-1)^{|T| + 1} E[min(T)] ]

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


    Kth MIN-MAX 反演

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

    构造容斥系数

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

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

    [kth max(S) = sum_{T subseteq S} f_{|T|} min(T) ]

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

    那么表达出来就是

    [sum_{j = 1}^{i - 1} {i - 1 choose j} f_{j + 1} = [i = k] ]

    二项式反演

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

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

    二项式反演:

    假设数列 (f)(g) 满足:

    [g_i = sum_{j = 1}^{i} {i choose j} f_j ]

    那么就有

    [f_i = sum_{j = 1}^{i} (-1)^{i - j} {i choose j} g_j ]

    可以考虑生成函数证明,设 (f_0 = g_0 = 0)

    前者就为 (G = F * e^x) 后者为 (F = G * e^{-x}) ,显然是等价的。

    那么对于前面那个式子,我们设 (g_{i - 1} = [i = k], f'_j = f_{j + 1})

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

    [f'_{i} = sum_{j = 0}^{i} (-1)^{i - j} {i choose j} g_j ]

    [f_{i + 1} = (-1)^{i - (k - 1)} {i choose k - 1} ]

    随意整理一下就有

    [f_i = (-1)^{i - k} {i - 1 choose k - 1} ]

    结论

    最终我们就可以如下求 (kth max)

    [kth max(S) = sum_{T subseteq S} (-1)^{|T| - k} {|T| - 1 choose k - 1} min(T) ]

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

    例题

    Luogu P4707 重返现世

    题意

    给定集合 (S) 中每个元素出现的概率 (displaystyle frac{p_i}{m}) ,其和为(1) ,每次会按概率出现,每次会按概率出现一个元素。

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

    (|S| = n) 满足限制

    (n le 1000, left| n - k ight| le 10, 1 le sum p = m le 10000)

    题解

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

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

    参考 此处 就可以得到

    [E(T) = frac{1}{sum_{i in T} frac{p_i}{m}} ]

    整理一下

    [E(T) = frac{m}{sum_{i in T} p_i} ]

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

    但是我们发现 (m) 不大,那么我们可以对于每个 (sum_{i in T} p_i) 求得其系数解决。

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

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

    (f_{i, j}) 表示当前选定的集合的 (p) 的和为 (i) ,组合数下面那里 (k=j)

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

    • 不加入,直接转上去 (f_{i, j} o f'_{i, j})

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

      (f) 的形式为 (displaystyle f_{j - p, k - 1} = sum_i (-1)^{i - (k - 1)} {i - 1 choose k - 2} g_{i, j - p})

      那么就会导致集合大小强制多 (1) ,并且概率和变成 (j) ,那么就是 (displaystyle sum_i (-1)^{i-k}{ichoose k-1} g_{i, j - p})

      接下来 强行 凑组合数递推形式 (displaystyle {nchoose m}={n-1choose m}+{n-1choose m-1}) ,把 (displaystyle {i choose k - 1}) 变为 (displaystyle {i choose k})

      那么我们要减去的其实就是 (displaystyle -sum(-1)^{i-k}{i-1choose k-1} g_{i, j - p}) ,其实就是 (- f_{j - p, k})

    那么最后的转移就是 (f'_{i,j} + f'_{i - p, j - 1} - f'_{i - p, j} o f_{i, j})

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

    复杂度是 (O(n(n - k)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
    ", ans);
    
        return 0;
    
    }
    
  • 相关阅读:
    Python—将py文件编译成so文件
    Python—网络通信编程之套接字基本原理
    Python—IO多路复用之epoll总结
    Python—IO多路复用之poll总结
    Python—IO多路复用之select总结
    希尔排序
    直接插入排序
    冒泡排序
    选择排序(C++,Java,Python实现)
    二叉搜索树的相关功能的实现
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/10252459.html
Copyright © 2020-2023  润新知