【期望 数学】7.6神经衰弱
OI方面的数学还是很玄学……
题目描述
天然少女小雪非常喜欢玩一个叫做神经衰弱的游戏。
游戏规则是,有若干种牌,每种牌有若干对,开始时全都正面朝下放置。
然后每次同时翻开两张牌,假如这两张牌是同一种类,则拿走这两张牌,否则再次翻回背面。
小雪虽然看上去傻乎乎的但是玩这个游戏非常厉害,所以可以认为她是绝对聪明的,即会采取最优决策和有着完美的记忆力
现在小雪想要知道,对于某一副牌局,她期望拿多少次可以拿走所有牌。
需要注意的是小雪玩的神经衰弱和普通神经衰弱有所不同。普通神经衰弱是依次拿走两张牌,而小雪的神经衰弱是同时拿走两张牌。
输入格式
第一行一个整数nn表示牌的种类数第二行nn个整数aiai表示第ii种牌有多少对
输出格式
一个整数,表示期望mod998244353即假如答案是a/ba/b,你需要输出的是某个数xx,使x∗b=a(mod998244353)保证a≠0,b≠0
样例输入
2
1 1
样例输出
332748121
样例解释
期望是10/3
数据规模与约定
30%:n≤5,ai≤2n≤5,ai≤2
60%:n≤3000n≤3000
对于所有数据n≤106,ai≤109
题目分析
期望题……看见期望题就很虚……
考场上去杠T2的贪心分,结果T3的30分爆搜都没打完。
暴力想法
当然就是把所有情况都枚举一遍,然后逆元写写就好了。
std题解
不难发现,当发现两张相同的牌时直接拿掉和最后拿掉并没有区别。所以我们可以认为先把所有牌翻一遍再考虑消去一定最优。那么我们只要计算在第一遍翻牌时恰好翻出的两张是相同的牌的概率。因为每次翻牌操作是等价的,所以可以直接把每次翻牌都当做第一次计算期望。
反正……我好像没怎么看懂,但是出题人讲解的还是很有道理的。
第一种理解
首先令$m=\sum{}_{}{ai}$,即总共有多少对牌。那么无论如何都至少要抽m次牌,并且,总的抽取次数不会超过2m。
有了答案下界的基础,那就可以想到$ans=m+(k1+k2+k3+…+kn)$,其中$k_i$表示第$i$种牌在全部牌都知道之后的期望抽取次数。
令$k=\sum{}_{}{ki}$
这一步化简看上去好像没什么用,不就只是提出来一个m吗,但是实际上提出m后$k_i$的约束就不同了(此时全部牌都知道了),求起来也方便许多。
我们分析一下抽了$m$次牌之后,为什么还会继续抽k次。那么当然是因为在前$m$次抽的时候,出现了抽$(x,y)$这两张牌而$x!=y$的事情。
自然考虑补集转化,转而去求之前的$m$次中,期望多少次抽到相同的一组。
嘛,这一步似乎就简单了很多很多嘛。
最后的公式就是
第二种理解
我们知道答案的上界是$2m$,不妨从上界推回答案。
上界情况当且仅当出现在最初$m$次抽牌时,一次都没有消除。
那么只需要计算出之前都没有被消除的期望就好了。
道理同上,公式是
1 #include<bits/stdc++.h> 2 typedef long long ll; 3 const ll MO = 998244353; 4 const int maxn = 1000035; 5 6 ll tot,ans; 7 int a[maxn],n; 8 9 ll qmi(ll a, ll b) 10 { 11 ll ret = 1; 12 while (b) 13 { 14 if (b&1) ret = ret*a%MO; 15 a = a*a%MO; 16 b >>= 1; 17 } 18 return ret; 19 } 20 int read() 21 { 22 char ch = getchar(); 23 int num = 0; 24 bool fl = 0; 25 for (; !isdigit(ch); ch = getchar()) 26 if (ch=='-') fl = 1; 27 for (; isdigit(ch); ch = getchar()) 28 num = (num<<1)+(num<<3)+ch-48; 29 if (fl) num = -num; 30 return num; 31 } 32 int main() 33 { 34 freopen("card.in","r",stdin); 35 freopen("card.out","w",stdout); 36 n = read(); 37 for (int i=1; i<=n; i++) a[i] = read(), tot = (tot+a[i])%MO; 38 for (int i=1; i<=n; i++) ans = (ans+2ll*a[i]*(2ll*a[i]-1ll)%MO)%MO; 39 ans = (qmi(2ll*tot*(2ll*tot-1ll)%MO, MO-2ll)*ans%MO)*tot%MO; 40 printf("%lld\n",(2ll*tot-ans+MO)%MO); 41 return 0; 42 }
END