loj2541 「PKUWC2018」猎人杀
自己是有多菜啊,10天前做的题,当时还是看了题解,还让NicoDafaGood同学给我讲了一下。
而我现在忘得一干二净,一点都想不起来了……
主要是当时听懂了就打了,没有总结啊。
我们发现,我们设集合AA的ww之和是SASA
那么一个集合AA在1之后死的概率是w1SA+w1w1SA+w1。
为什么呢。
虽然每次选下一个会死的人,是从没死的人中选,但是实际上,也可以是所有人中选,如果选到了死了的人就继续选。
记得很久以前谁讲过,其实这道题,换一种说法是,
一个序列,有wiwi个ii,把这一共∑w∑w个数随机排列,第ii个人死的时候是ii序列里第一次出现的位置。
问第一个人最后死的概率。
或许有点抽象,我们换成数学方式来说:
设PiPi是下一个死的是第ii个人的概率
那么Pi=wi∑w−SDPi=wi∑w−SD,其中DD是死了的人的集合。
令A=∑w,B=SDA=∑w,B=SD,
把式子变换一下,我们可以得到Pi=BAPi+wiAPi=BAPi+wiA,
就是说有BABA的可能,我们这次做完之后ii还没死,相当于我们攻击到了已死的人。
那么集合AA都在1之后死的概率这样算:
P=∞∑i=0(1−SA+w1∑w)iw1∑w=w1∑w∞∑i=0(1−SA+w1∑w)i=w1∑w×11−1+SA+w1∑w=w1SA+w1P=∞∑i=0(1−SA+w1∑w)iw1∑w=w1∑w∞∑i=0(1−SA+w1∑w)i=w1∑w×11−1+SA+w1∑w=w1SA+w1
接下来我们就需要枚举在1死之后的人的集合的SS,就是ww和,然后容斥。
用分治NTT就可以了。
诶,差点又忘了总结。
主要是,算1是最后死的概率不好算,但是算一个人是在一个集合中最先死的概率好算。
很容易想到容斥一下,然后做背包这玩意可以用NTT,维护一个堆,每次用最小的那两个合并
然后我们发现∑w∑w很小,也非常让人舒服。
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | //Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #include<vector> #include<set> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) const int maxn=1e6+7; const ll mod=998244353,PR=3; ll n,w[maxn],A[maxn],B[maxn],len,l; char cc;ll ff; template < typename T> void read(T& aa) { aa=0;cc= getchar ();ff=1; while ((cc< '0' ||cc> '9' )&&cc!= '-' ) cc= getchar (); if (cc== '-' ) ff=-1,cc= getchar (); while (cc>= '0' &&cc<= '9' ) aa=aa*10+cc- '0' ,cc= getchar (); aa*=ff; } vector<ll> o; struct Node{ int len; vector<ll> P; Node( int len,vector<ll> P):len(len),P(P){} bool operator < ( const Node& b) const { return len<b.len;} }; multiset<Node> G; multiset<Node>::iterator it1,it2; ll qp(ll x,ll k) { ll rs=1; while (k) { if (k&1) rs=rs*x%mod; k>>=1; x=x*x%mod; } return rs; } ll finv(ll x) { return qp(x,mod-2);} ll qp1(ll x,ll k) { if (k<0) return qp(finv(x),-k); return qp(x,k); } void Rader(ll F[],ll len) { for ( int i=1,j=len/2,k;i<len-1;++i) { if (i<j) swap(F[i],F[j]); k=len>>1; while (j>=k) {j-=k;k>>=1;} if (j<k) j+=k; } } void FFT(ll F[],ll len,ll on) { Rader(F,len); for ( int h=2;h<=len;h<<=1) { ll wn=qp1(PR,(mod-1)*on/h); for ( int j=0;j<len;j+=h) { ll w=1; for ( int i=j;i<j+h/2;++i) { ll u=F[i],v=F[i+h/2]*w%mod; F[i]=(u+v)%mod; F[i+h/2]=(u-v+mod)%mod; w=w*wn%mod; } } } ll x=finv(len); if (on==-1) For(i,0,len) F[i]=F[i]*x%mod; } int main() { read(n); For(i,1,n) read(w[i]),w[0]+=w[i]; For(i,2,n) { o.clear(); o.push_back(1); For(j,1,w[i]-1) o.push_back(0); o.push_back(mod-1); G.insert(Node(w[i],o)); } while (G.size()>1) { it1=it2=G.begin(); ++it2; l=it1->len+it2->len; for (len=1;len<=l;len<<=1); For(i,0,it1->len) A[i]=it1->P[i]; For(i,0,it2->len) B[i]=it2->P[i]; G.erase(G.begin()); G.erase(G.begin()); FFT(A,len,1); FFT(B,len,1); For(i,0,len) A[i]=A[i]*B[i]%mod; FFT(A,len,-1); o.clear(); o.reserve(l+1); o.assign(&A[0],&A[l+1]); G.insert(Node(l,o)); For(i,0,len) A[i]=B[i]=0; } ll ans=0; it1=G.begin(); For(i,0,w[0]-w[1]) ans+=w[1]*finv(i+w[1])%mod*it1->P[i]%mod; printf ( "%lld\n" ,ans%mod); return 0; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 聊一聊 操作系统蓝屏 c0000102 的故障分析
· SQL Server 内存占用高分析
· .NET Core GC计划阶段(plan_phase)底层原理浅谈
· .NET开发智能桌面机器人:用.NET IoT库编写驱动控制两个屏幕
· 用纯.NET开发并制作一个智能桌面机器人:从.NET IoT入门开始
· 我干了两个月的大项目,开源了!
· 推荐一款非常好用的在线 SSH 管理工具
· 聊一聊 操作系统蓝屏 c0000102 的故障分析
· 千万级的大表,如何做性能调优?
· .NET周刊【1月第1期 2025-01-05】