loj2541 「PKUWC2018」猎人杀

https://loj.ac/problem/2541

自己是有多菜啊,10天前做的题,当时还是看了题解,还让NicoDafaGood同学给我讲了一下。

而我现在忘得一干二净,一点都想不起来了……

主要是当时听懂了就打了,没有总结啊。

我们发现,我们设集合$A$的$w$之和是$S_A$

那么一个集合$A$在1之后死的概率是$\frac{w_1}{S_A+w_1}$。

为什么呢。

虽然每次选下一个会死的人,是从没死的人中选,但是实际上,也可以是所有人中选,如果选到了死了的人就继续选。

记得很久以前谁讲过,其实这道题,换一种说法是,

一个序列,有$w_i$个$i$,把这一共$\sum w$个数随机排列,第$i$个人死的时候是$i$序列里第一次出现的位置。

问第一个人最后死的概率。

或许有点抽象,我们换成数学方式来说:

设$P_i$是下一个死的是第$i$个人的概率

那么$P_i=\frac{w_i}{\sum w-S_D}$,其中$D$是死了的人的集合。

令$A=\sum w,B=S_D$,

把式子变换一下,我们可以得到$P_i=\frac{B}{A} P_i + \frac{w_i}{A}$,

就是说有$\frac{B}{A}$的可能,我们这次做完之后$i$还没死,相当于我们攻击到了已死的人。

那么集合$A$都在1之后死的概率这样算:

\begin{aligned}
P &= \sum\limits_{i = 0}^{\infty} (1 - \frac{S_A + w_1}{\sum w})^{i} \frac{w_1}{\sum w} \\
&= \frac{w_1}{\sum w}\sum\limits_{i = 0}^{\infty} (1 - \frac{S_A + w_1}{\sum w})^{i} \\
&= \frac{w_1}{\sum w} \times \frac{1}{1 - 1 + \frac{S_A + w_1}{\sum w}} \\
&= \frac{w_1}{S_A + w_1}
\end{aligned}

接下来我们就需要枚举在1死之后的人的集合的$S$,就是$w$和,然后容斥。

用分治NTT就可以了。

诶,差点又忘了总结。

主要是,算1是最后死的概率不好算,但是算一个人是在一个集合中最先死的概率好算。

很容易想到容斥一下,然后做背包这玩意可以用NTT,维护一个堆,每次用最小的那两个合并

然后我们发现$\sum w$很小,也非常让人舒服。

//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;
}

原文地址:https://www.cnblogs.com/Serene-shixinyi/p/9210599.html

时间: 2024-11-06 07:25:43

loj2541 「PKUWC2018」猎人杀的相关文章

Loj #2541「PKUWC2018」猎人杀

Loj #2541. 「PKUWC2018」猎人杀 题目链接 好巧妙的题! 游戏过程中,概率的分母一直在变化,所以就非常的不可做. 所以我们将问题转化一下:我们可以重复选择相同的猎人,只不过在一个猎人被选择了过后我们就给他打上标记,再次选择他的时候就无效.这样与原问题是等价的. 证明: 设\(sum=\sum_iw_i,kill=\sum_{i被杀死了}w_i\). 攻击到未被杀死的猎人\(i\)的概率为\(P\). 则根据题意\(P=\frac{w_i}{sum-kill}\). 问题转化后:

「PKUWC2018」猎人杀(分治NTT+概率期望)

Description 猎人杀是一款风靡一时的游戏"狼人杀"的民间版本,他的规则是这样的: 一开始有 \(n\) 个猎人,第 \(i\) 个猎人有仇恨度 \(w_i\) ,每个猎人只有一个固定的技能:死亡后必须开一枪,且被射中的人也会死亡. 然而向谁开枪也是有讲究的,假设当前还活着的猎人有 \([i_1,i_2,...,i_m]\),那么有 \(\frac{w_{i_k}}{\sum_{j=1}^nw_{i_j}}\) 的概率是向猎人 \(k\) 开枪. 一开始第一枪由你打响,目标的选

「PKUWC2018」猎人杀(概率+容斥+分治NTT)

https://loj.ac/problem/2541 很有意思的一道题目. 直接去算这题话,因为分母会变,你会发现不管怎么样都要枚举顺序. 考虑把题目转换,变成分母不会变的,即对于一个已经删过的,我们不把它从分母中剔除,但是,每一次的选择需要一直选直到选了一个没有被删过的. 然后再考虑怎么计算,这时就可以容斥了: 1既然要最后删除,我们枚举一个集合S一定在它之后被删,其它的随意. 设\(sw\)为\(\sum_{i\in S}w[i]\),\(W=\sum_{i=1}^n w[i]\) 最后答

Loj #2542. 「PKUWC2018」随机游走

Loj #2542. 「PKUWC2018」随机游走 题目描述 给定一棵 \(n\) 个结点的树,你从点 \(x\) 出发,每次等概率随机选择一条与所在点相邻的边走过去. 有 \(Q\) 次询问,每次询问给定一个集合 \(S\),求如果从 \(x\) 出发一直随机游走,直到点集 \(S\) 中所有点都至少经过一次的话,期望游走几步. 特别地,点 \(x\)(即起点)视为一开始就被经过了一次. 答案对 $998244353 $ 取模. 输入格式 第一行三个正整数 \(n,Q,x\). 接下来 \(

【PKUWC2018】猎人杀

题目描述 题目分析 设\(W=\sum\limits_{i=1}^nw_i\),\(A=\sum\limits_{i=1}^nw_i[i\ is\ alive]\),\(P_i\)为下一个打中\(i\)的概率. 如果开枪打中了已经死亡的猎人,我们可以视作再开一枪,这样就不会产生影响,因此有 \[ \begin{split} P_i&=\frac{W-A}{W}P_i+\frac{w_i}W\移项得\ P_i&=\frac{w_i}{A} \end{split} \] 考虑容斥,枚举\(S\

「PKUWC2018」随机游走

题面在这里! 显然你如果直接带一个子集到树上dp的话复杂度是会炸上天的23333. 考虑期望也是可以进行min_max容斥的,也就是: max{S} = ∑ min{T} * (-1) ^( |T|+1 ) ,其中T是S的一个非空子集,max{S}和min{S}分别代表集合中所有条件都被满足的期望时间 和 集合中至少有一个条件被满足的期望时间, 当然对本题来说就是 所有钦定的点都被到过一次的期望时间 和 第一次到某个钦定的点的期望时间.... 发现min非常的好算,对于每个集合直接一次树上dp

loj2537 「PKUWC2018」Minimax 【概率 + 线段树合并】

题目链接 loj2537 题解 观察题目的式子似乎没有什么意义,我们考虑计算出每一种权值的概率 先离散化一下权值 显然可以设一个\(dp\),设\(f[i][j]\)表示\(i\)节点权值为\(j\)的概率 如果\(i\)是叶节点显然 如果\(i\)只有一个儿子直接继承即可 如果\(i\)有两个儿子,对于儿子\(x\),设另一个儿子为\(y\) 则有 \[f[i][j] += f[x][j](1 - p_i)\sum\limits_{k > j}f[r][k] + f[x][j]p_i\sum\

loj#2537. 「PKUWC2018」Minimax

传送门 感觉我去pkuwc好像只有爆零的份-- 设\(f_{u,i}\)表示\(u\)取到\(i\)的概率,那么有如下转移 \[f_{u,i}=f_{ls,i}(p_u\sum_{j<i}f_{rs,j}+(1-p_u)\sum_{j>i}f_{rs,j})+\\f_{rs,i}(p_u\sum_{j<i}f_{ls,j}+(1-p_u)\sum_{j>i}f_{ls,j})\] 然后用线段树合并即可,最后在根节点的线段树上\(dfs\)统计答案 //minamoto #inclu

loj#2540. 「PKUWC2018」随机算法

传送门 完了pkuwc咋全是dp怕是要爆零了-- 设\(f(S)\)表示\(S\)的排列数,\(S\)为不能再选的点集(也就是选到独立集里的点和与他们相邻的点),\(mx(S)\)表示\(S\)状态下对应的独立集大小,枚举点\(i\),如果\(i\)不在\(S\)里,分情况考虑,设\(w[i]\)表示点\(i\)以及与之相邻的点,\(T=S|w[i]\),\(sz[S]\)表示二进制\(S\)有多少个\(1\),如果\(mx[T]=mx[S]+1\),那么\[f[T]+=f[S]\times A