G - Colorful Candies 2 N 个 有色糖果,第i个颜色c[i]
从中选K个有 binom(N,K)种方案
等概率选一种方案
价值=所选的颜色的不同的数量
对于每个 k= 1…N 求期望价值
mod 998244353
限制 N 5e4
c[i] [1,1e9]
4s
1024mb
我的思路 首先只关心不同值,显然c[i]可以离散化到[1..N]
答案 = sum{价值} / binom(N,K)
不同颜色互不影响
所以 选了j种颜色, 一共k个, 如果能算方案出来 f(j), 那么答案 = sum j * f(j)
指定的 c[…] 中选的话
似乎可以卷积
去表示每个颜色 选t个的方案数, 然后卷积意义是前 j 种颜色(可能有的不选) 选k个的方案数
好像无法获得选了几个颜色
题解 反过来, 也就是每个颜色出现一次的概率 的贡献和
P(出现一次) = 1-P(一次都不出现)
binom(n-x,k) / binom(n,k) , 也就是x 是这个颜色的个数
其中 n-x < k 的话, 必定出现p = 1
(binom(n,k) - binom(n-x,k))/binom(n,k) , 也就是x 是这个颜色的个数
可以减少计算
注意到 可以统计个数为x的有多少个, 这样最多 $\sqrt(N)$个统计
因此对于k来讲,是$O(\sqrt{N})$的
总的便是$O(N^{\frac{3}{2}})$
代码 https://atcoder.jp/contests/abc215/submissions/33712411
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <bits/stdc++.h> #include <atcoder/all> using mint = atcoder::modint998244353;using namespace std;typedef long long ll;const int MOD = 998244353 ;#define rep(i,a,n) for (ll i=a;i<(ll)n;i++) #define per(i,a,n) for (ll i=n;i-->(ll)a;) #define pb push_back ll read () {ll r;scanf ("%lld" ,&r);return r;} int c[50010 ];mint fac[50010 ] = {1 }; mint invv[50010 ] = {0 ,1 }; mint invfac[50010 ] = {1 }; mint binom (int n,int m) { if (m > n) return 0 ; return fac[n] * invfac[m] * invfac[n-m]; } int main () { int n = read (); rep (i,0 ,n) c[i] = read (); rep (i,1 ,n+1 ) fac[i] = fac[i-1 ] * i; invv[1 ] = 1 ; rep (i,2 ,n+1 ) invv[i] = (MOD - MOD/i) * invv[MOD%i]; rep (i,1 ,n+1 ) invfac[i] = invfac[i-1 ] * invv[i]; sort (c,c+n); vector<int > sz = {1 }; rep (i,1 ,n){ if (c[i] != c[i-1 ]){ sz.push_back (1 ); }else { sz.back ()++; } } sort (sz.begin (),sz.end ()); vector<pair<int ,int >> sc; int cnt = 0 ; rep (i,0 ,sz.size ()){ cnt++; if (i+1 == (int )sz.size () || sz[i] != sz[i+1 ]){ sc.push_back ({sz[i],cnt}); cnt = 0 ; } } rep (k,1 ,n+1 ){ mint bnk = binom (n,k); mint ans = bnk * sz.size () ; for (auto [s,t]:sc) { if (n-s < k) break ; ans -= binom (n-s,k) * t; } ans /= bnk; printf ("%d\n" ,ans.val ()); } return 0 ; }
G - Cabbage Master N种菜,每种 A[i] 个
M个需求, 每个需求B[i] 个, 但是限制c[i][j] = 0/1
表示第i个需求 是否允许得到 第j种菜
如果 能满足所有需求则 成功
现在要尽量少的删除一些菜的个数, 让它无法成功
并且求, 删除同样数量的方案数 mod 998244353
限制 n 20
m 1e4
a[i] 1e5
b[i] 1e5
3s
1024mb
我的思路 一眼感觉网络流, 但是看着这n这么少
又觉得说 会不会是 maskdp
2^20 = 1048576, 大概1e6
网络流思路
就是 S -> Ini 流量 A[i]
Ini -> Outj 流量无限 如果c[i][j] == 1
Outj -> T 流量B[i]
那么目标是让最小割(最大流) <= sum B[i] 即可
题解 二分图
左侧N个颜色, 右侧M个需求
注意到 这里成功对应的是 匹配 = sum 右侧
所以是枚举右侧的的点集,看对应左侧的点集是否大于等于右侧
左侧L, 右侧R
要能完美匹配 $\forall S \subset R $
左侧对应集合并的和 $\ge S$对应需求的和
即 min (左侧对应集合并的和 - S对应需求的和) >= 0
n=20, 考虑枚举左侧的并,来找右侧的max
但似乎通过枚举子集可能有m 2^n 复杂度
但实际上我们要的是
min {f(L0) - g(L0)} >= 0, 其中f 算的集合里左侧的和, g 算的映射到左侧包含于集合的右侧的值的和 (既然B[i] 都是正的,那就是所有的加起来让g达到最大
g中计算 子集和可以sosdp 高维前缀和
那么删除数量X = min( f(L0) - g(L0)) + 1
然后问题变成如何计算方案数
ans = 0 , 不操作1种
对于ans > 0
设 左侧移除的X的值 来自的点集合恰好为S
当存在 S1 满足 S 是 S1 的子集, 且 f(L0) - g(S1) + 1 == X, 这时 S移除X的方案数h(S) 会有贡献
binom(S的个数,X) = sum h(T), T 是S的子集
这就是 子集反演问题
$h(S) = \sum (-1)^{|S|-|T|} binom(T的个数,X)$, T是S的子集
又是求子集的函数和, 那么这里把和原集合有关的移动一下
$(-1)^{|S|} {h(S)} = \sum (-1)^{|T|} \binom{f(T)}{X}$, T是S的子集
同样FWT, SOSDP可以处理
子集反演 $g(S) = \sum f(T)$, T是S的子集合
$f(S) = \sum (-1)^{|S| - |T|} g(T)$ , T是S的子集合
代码 https://atcoder.jp/contests/abc215/submissions/33719233
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <bits/stdc++.h> #include <atcoder/all> using mint = atcoder::modint998244353;using namespace std;typedef long long ll;#define rep(i,a,n) for (ll i=a;i<(ll)n;i++) #define per(i,a,n) for (ll i=n;i-->(ll)a;) ll read () {ll r;scanf ("%lld" ,&r);return r;} const ll INF = 0x3f3f3f3f ; const int N = 20 ;const int MASK = ( 1 << N ) ;const int M = N * 100000 ;mint fac[M+10 ] = {1 }; mint ifac[M+10 ] ={1 }; int f[MASK+10 ]; int g[MASK+10 ]; mint h[MASK+10 ]; int cnt[MASK+10 ]; int cont[MASK+10 ]; int B[M+10 ]; int adj[M+10 ]; mint binom ( int n, int m ) { return n < m ? 0 : fac[n] * ifac[m] * ifac[n - m]; }int main () { rep (i,1 ,M+1 ) fac[i] = fac[i - 1 ]*i; ifac[M] = fac[M].inv (); per (i,0 ,M) ifac[i] = ifac[i + 1 ]*(i + 1 ); int n = read (); int m = read (); rep (i,0 ,n)f[1 << i]=read (); rep (j,0 ,m)B[j]=read (); rep (i,0 ,n)rep (j,0 ,m) if (read ())adj[j]|=1 << i; rep (j,0 ,m)g[adj[j]] += B[j]; rep (mask,1 ,1 << n) cnt[mask] = cnt[mask >> 1 ] + ( mask & 1 ); rep (mask,1 ,1 << n) f[mask] = f[mask&(-mask)] + f[mask&(mask-1 )]; rep (pwr,0 ,n) rep (mask,1 ,1 << n) if (mask & (1 << pwr)) g[mask] += g[mask ^ (1 << pwr)]; rep (mask,0 ,1 << n) if (!g[mask]) g[mask] = -INF; int X = INF; rep (mask,1 ,1 << n) X = min (X,f[mask]-g[mask]+1 ); if (max (X,0 ) == 0 ) { printf ( "0 1\n" ); return 0 ; } rep (mask,1 ,1 << n) h[mask] = binom (f[mask], X) * (cnt[mask] & 1 ? -1 : 1 ); rep (pwr,0 ,n) rep (mask,0 ,1 << n) if (mask & (1 << pwr)) h[mask] += h[mask ^ (1 << pwr)]; rep (mask,0 ,1 << n) h[mask] = h[mask] * ((cnt[mask] & 1 )?-1 :1 ); rep (mask,1 ,1 << n) if ( f[mask] - g[mask] + 1 == X ) cont[mask] = 1 ; rep (pwr,0 ,n) rep (mask,0 ,1 << n) if (mask & (1 << pwr)) cont[mask ^ (1 << pwr)] += cont[mask]; mint ans = 0 ; rep (mask,0 ,1 << n) if (cont[mask]) ans += h[mask]; printf ("%d %d\n" ,X, ans.val ()); return 0 ; }
总结 G
其实还算基础知识点, 如何批量算模拟元,批量阶乘和阶乘模逆元,如何基于它们快速bionom
然后就是概率统计变形状和相同次数统计变成$\sqrt{N}$
评分 2267 也差不多
H
二分图,霍尔(Hall)定理
二分图一定程度上, 就不在意初始设计的方向了, 因为是匹配, 内部是没有关系的
霍尔定理对于这种大于1流量的也适用(因为从本质上看 左/右侧最多k个, 无非是k个点, 有无穷大边, 无非是这些拆开后的左右侧按照原来的关系两两有边, 而这个思路是不是特殊题型还能反过来思考)
这种”任意”的条件,可以考虑是在哪个部分将它破坏的
还涉及到 子集反演(以及用子集反演完成父集合反演)
参考 官方题解
hall’s theorem