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

https://loj.ac/problem/2541

很有意思的一道题目。

直接去算这题话,因为分母会变,你会发现不管怎么样都要枚举顺序。

考虑把题目转换,变成分母不会变的,即对于一个已经删过的,我们不把它从分母中剔除,但是,每一次的选择需要一直选直到选了一个没有被删过的。

然后再考虑怎么计算,这时就可以容斥了:

1既然要最后删除,我们枚举一个集合S一定在它之后被删,其它的随意。

设\(sw\)为\(\sum_{i\in S}w[i]\),\(W=\sum_{i=1}^n w[i]\)

最后答案就是\(\sum_{S}(-1)^{|S|}\sum_{i=0}^{∞}({W-sw-w[1] \over W})^i*{w[1] \over W}\)

\(=\sum_S (-1)^{|S|}{W\over sw+w[1]}*{w[1]\over W}\)

\(=\sum_S (-1)^{|S|} {w[1] \over sw+w[1]}\)

用一个分治NTT优化一下背包就好了。

Code:


#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

const int mo = 998244353;

ll ksm(ll x, ll y) {
	ll s = 1;
	for(; y; y /= 2, x = x * x % mo)
		if(y & 1) s = s * x % mo;
	return s;
}

#define V vector<ll>
#define si size()
#define re resize

namespace ntt {
	const int nm = 131072;
	int r[nm]; ll w[nm], a[nm], b[nm];
	void build() {
		for(int i = 1; i < nm; i *= 2) {
			w[i] = 1; ll v = ksm(3, (mo - 1) / 2 / i);
			ff(j, 1, i) w[i + j] = w[i + j - 1] * v % mo;
		}
	}
	void dft(ll *a, int n, int f) {
		ff(i, 0, n) {
			r[i] = r[i / 2] / 2 + (i & 1) * (n / 2);
			if(i < r[i]) swap(a[i], a[r[i]]);
		} ll b;
		for(int i = 1; i < n; i *= 2) for(int j = 0; j < n; j += 2 * i) ff(k, 0, i)
			b = a[i + j + k] * w[i + k], a[i + j + k] = (a[j + k] - b) % mo, a[j + k] = (a[j + k] + b) % mo;
		if(f == -1) {
			reverse(a + 1, a + n);
			b = ksm(n, mo - 2);
			ff(i, 0, n) a[i] = (a[i] + mo) * b % mo;
		}
	}
	V operator * (V p, V q) {
		int n0 = p.si + q.si - 1, n = 1;
		while(n < n0) n *= 2;
		ff(i, 0, n) a[i] = b[i] = 0;
		ff(i, 0, p.si) a[i] = p[i];
		ff(i, 0, q.si) b[i] = q[i];
		dft(a, n, 1); dft(b, n, 1);
		ff(i, 0, n) a[i] = a[i] * b[i] % mo;
		dft(a, n, -1);
		p.re(n0);
		ff(i, 0, p.si) p[i] = a[i];
		return p;
	}
}

using ntt :: operator *;

const int N = 1e5 + 5;

int n, w[N];

V dg(int x, int y) {
	if(x > y) {
		V p; p.re(1); p[0] = 1;
		return p;
	}
	if(x == y) {
		V p; p.clear(); p.re(w[x] + 1);
		p[0] = 1; p[w[x]] = -1;
		return p;
	}
	int m = x + y >> 1;
	return dg(x, m) * dg(m + 1, y);
}

int main() {
	ntt :: build();
	scanf("%d", &n);
	fo(i, 1, n) scanf("%d", &w[i]);
	V p = dg(2, n);
	ll ans = 0;
	ff(i, 0, p.si) ans = (ans + p[i] * ksm(i + w[1], mo - 2) % mo * w[1]) % mo;
	ans = (ans % mo + mo) % mo;
	pp("%lld\n", ans);
}

原文地址:https://www.cnblogs.com/coldchair/p/12620161.html

时间: 2024-08-26 00:31:49

「PKUWC2018」猎人杀(概率+容斥+分治NTT)的相关文章

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\) 开枪. 一开始第一枪由你打响,目标的选

loj2541 「PKUWC2018」猎人杀

https://loj.ac/problem/2541 自己是有多菜啊,10天前做的题,当时还是看了题解,还让NicoDafaGood同学给我讲了一下. 而我现在忘得一干二净,一点都想不起来了…… 主要是当时听懂了就打了,没有总结啊. 我们发现,我们设集合$A$的$w$之和是$S_A$ 那么一个集合$A$在1之后死的概率是$\frac{w_1}{S_A+w_1}$. 为什么呢. 虽然每次选下一个会死的人,是从没死的人中选,但是实际上,也可以是所有人中选,如果选到了死了的人就继续选. 记得很久以前

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 #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\

【BZOJ2969】矩形粉刷 概率+容斥

[BZOJ2969]矩形粉刷 Description 为了庆祝新的一年到来,小M决定要粉刷一个大木板.大木板实际上是一个W*H的方阵.小M得到了一个神奇的工具,这个工具只需要指定方阵中两个格子,就可以把这两格子为对角的,平行于木板边界的一个子矩形全部刷好.小M乐坏了,于是开始胡乱地使用这个工具. 假设小M每次选的两个格子都是完全随机的(方阵中每个格子被选中的概率是相等的),而且小M使用了K次工具,求木板上被小M粉刷过的格子个数的期望值是多少. Input 第一行是整数K,W,H Output 一

「PKUWC2018」随机游走

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

luoguP5644 [PKUWC2018]猎人杀 概率期望+生成函数+NTT

好神仙的概率题啊 我感觉第一步转化好难想,但是想出来第一步的转化方法的话后面用生成函数算背包就很简单了. code: #include <cmath> #include <cstring> #include <algorithm> #include <cstdio> #include <string> #define ll long long #define ull unsigned long long using namespace std;