题目链接

https://loj.ac/problem/565

题解

首先,若进行所有操作之后成功执行的操作数为 \(m\),最终得到的数为 \(w\),那么发生改变的二进制位的数量之和(即代价之和)为 \(2m - {\rm bit}(w)\)。其中,\({\rm bit}(x)\) 表示 \(x\) 在二进制下 \(1\) 的个数。

证明:不难发现,一次操作会改变的二进制位的总数为进位次数\(+1\),因此执行 \(m\) 次操作后改变的二进制位的数量总和为总进位次数\(+m\)。由于一次进位会导致整个二进制数中 \(1\) 的个数减少 \(1\),因此若最终得到的数为 \(w\),那么 \(m - {\rm bit}(w)\) 即为总进位次数。故总代价为 \(2m - {\rm bit}(w)\)。不仅如此,该结论也同时告诉我们操作顺序与最终答案是无关的。

这样,本题转化为求 \(2m - {\rm bit}(w)\) 的期望值。考虑将其拆开计算,\(2m\) 的期望值显然为所有操作执行成功的概率和乘以 \(2\),\({\rm bit}(w)\) 的期望值可以根据期望的线性性质转化为所有操作后每一个二进制位上的数字最终为 \(1\) 的概率之和。这样,我们就只需要求出最终每一个二进制位上的数字为 \(1\) 的概率即可。

设 \(f_{i, j}\) 表示进行所有操作之后从小到大第 \(i\) 个二进制位(以下简称第 \(i\) 位)的值被改变了 \(j\) 次的概率。由于第 \(i\) 位最终的结果可能被在第 \(i\) 位本身的操作以及所有第 \(j(j < i)\) 位上的操作所影响,我们将两部分分开考虑,再设 \(g_{i, j}\) 表示进行完所有在第 \(i\) 位上的操作之后第 \(i\) 位的值被改变了 \(j\) 次的概率,这样,我们就能够用 \(g_i\) 与 \(f_{i - 1}\) 算出 \(f_i\)。

首先考虑如何计算 \(g_i\)。对于在第 \(i\) 位上的某个操作,若该操作成功执行的概率为 \(p\),那么有 \(g_{i, j} \leftarrow g_{i, j - 1} \times p + g_{i, j} \times (1 - p)\),显然该转移可视为两个多项式相乘,那么最终的结果多项式即为若干个一次多项式的乘积,可用堆优化 NTT 合并。这样,我们就能在 \(O(m \log^2 m)\) 的时间内求出整个 \(g\) 数组。

接下来考虑算 \(f\),由于在第 \(i - 1\) 位上的每两次改变才会导致第 \(i\) 位的一次改变,故转移为 \(f_{i, j} = \sum_\limits{\lfloor\frac{a}{2}\rfloor + b = j} f_{i - 1, a} + g_{i, b}\)。显然该转移也可视为两个多项式相乘,可用 NTT 优化。对于每个 \(i\),计算 \(f\) 数组时我们只需要暴力做 NTT 即可。以下是来自 yww 的时间复杂度证明:

记 \(c_i\) 表示所有操作中在第 \(i\) 位上的操作数量(那么有 \(\sum c_i = m\))。

算 \(f\) 的时间复杂度是

\[\begin{aligned}& O(\log m \times \sum_{i = 0}^n \sum_{j = 0}^i \frac{c_j}{2^{i - j}}) \\ =& O(\log m \times \sum_{i = 0}^{n} c_i \sum_{j = 0}^{i} 2^{-j}) \\ =& O(m \log m)\end{aligned}
\]

上面的时间复杂度分析基于以下两点:

  • 在第 \(j\) 位上的每 \(2^{i - j}\) 次改变才会导致第 \(i\) 位的一次改变(\(j < i\))。
  • \(\sum_\limits{i = 0}^{+\infty} 2^{-i} = 2\)。

这样,暴力用 NTT 求 \(f\) 的时间复杂度是可接受的,因此解决整个问题的时间复杂度为 \(O(m \log^2 m)\)。

代码

#include<bits/stdc++.h>

using namespace std;

const int N = 524288, mod = 998244353, G = 3;

void add(int& x, int y) {
x += y;
if (x > mod) {
x -= mod;
}
} void sub(int& x, int y) {
x -= y;
if (x < 0) {
x += mod;
}
} int mul(int x, int y) {
return (long long) x * y % mod;
} int qpow(int v, int p) {
int result = 1;
for (; p; p >>= 1, v = mul(v, v)) {
if (p & 1) {
result = mul(result, v);
}
}
return result;
} int n, m, a[N], b[N], c[N], rev[N], len, poly_a[N], poly_b[N];
vector<int> event[N], arr[N], f[N]; void ntt(int* c, int n, int type) {
for (int i = 0; i < n; ++i) {
if (i < rev[i]) {
swap(c[i], c[rev[i]]);
}
}
for (int i = 1; i < n; i <<= 1) {
int x = qpow(G, type == 1 ? (mod - 1) / (i << 1) : mod - 1 - (mod - 1) / (i << 1));
for (int j = 0; j < n; j += i << 1) {
int y = 1;
for (int k = 0; k < i; ++k, y = mul(y, x)) {
int p = c[j + k], q = mul(y, c[i + j + k]);
c[j + k] = (p + q) % mod;
c[i + j + k] = (p - q + mod) % mod;
}
}
}
if (type == -1) {
int inv = qpow(n, mod - 2);
for (int i = 0; i < n; ++i) {
c[i] = mul(c[i], inv);
}
}
} void mul(int len_a, int len_b, int* a, int* b, int* c) {
for (len = 0; (1 << len) <= len_a + len_b; ++len);
int m = 1 << len;
for (int i = 0; i < m; ++i) {
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << len - 1);
}
for (int i = 0; i < m; ++i) {
poly_a[i] = poly_b[i] = 0;
if (i <= len_a) {
poly_a[i] = a[i];
}
if (i <= len_b) {
poly_b[i] = b[i];
}
}
ntt(poly_a, m, 1);
ntt(poly_b, m, 1);
for (int i = 0; i < m; ++i) {
poly_a[i] = mul(poly_a[i], poly_b[i]);
}
ntt(poly_a, m, -1);
for (int i = 0; i < m; ++i) {
c[i] = poly_a[i];
}
} struct poly {
vector<int> poly_s; poly () {
poly_s.clear();
} bool operator < (const poly& a) const {
return poly_s.size() > a.poly_s.size();
}
}; int main() {
scanf("%d%d", &n, &m);
n += 19;
int ans = 0;
for (int i = 1; i <= m; ++i) {
int a, x, y;
scanf("%d%d%d", &a, &x, &y);
int p = mul(x, qpow(y, mod - 2));
event[a].push_back(p);
add(ans, p);
}
ans = (ans << 1) % mod; auto get_array = [&] (vector<int>& all) {
priority_queue<poly> s;
for (auto v : all) {
poly new_poly;
new_poly.poly_s.push_back((1 - v + mod) % mod);
new_poly.poly_s.push_back(v);
s.push(new_poly);
}
while (s.size() > 1) {
poly l = s.top();
s.pop();
poly r = s.top();
s.pop();
int len_a = l.poly_s.size() - 1, len_b = r.poly_s.size() - 1;
for (int i = 0; i <= len_a; ++i) {
a[i] = l.poly_s[i];
}
for (int i = 0; i <= len_b; ++i) {
b[i] = r.poly_s[i];
}
mul(len_a, len_b, a, b, c);
poly new_poly;
for (int i = 0; i <= len_a + len_b; ++i) {
new_poly.poly_s.push_back(c[i]);
}
s.push(new_poly);
}
return s.top().poly_s;
}; for (int i = 0; i <= n; ++i) {
if (event[i].size()) {
arr[i] = get_array(event[i]);
} else {
arr[i].push_back(1);
}
}
f[0] = arr[0];
int total_len = event[0].size();
for (int i = 1; i <= n; ++i) {
int old = total_len >> 1;
fill(a, a + old + 1, 0);
for (int j = 0; j <= total_len; ++j) {
add(a[j >> 1], f[i - 1][j]);
}
for (int j = 0; j <= event[i].size(); ++j) {
b[j] = arr[i][j];
}
mul(old, event[i].size(), a, b, c);
total_len = old + event[i].size();
for (int j = 0; j <= total_len; ++j) {
f[i].push_back(c[j]);
}
}
for (int i = 0; i <= n; ++i) {
for (int j = 1; j < f[i].size(); j += 2) {
sub(ans, f[i][j]);
}
}
printf("%d\n", ans);
return 0;
}

LOJ565. 「LibreOJ Round #10」mathematican 的二进制(NTT)的更多相关文章

  1. #565. 「LibreOJ Round #10」mathematican 的二进制(期望 + 分治NTT)

    题面 戳这里,题意简单易懂. 题解 首先我们发现,操作是可以不考虑顺序的,因为每次操作会加一个 \(1\) ,每次进位会减少一个 \(1\) ,我们就可以考虑最后 \(1\) 的个数(也就是最后的和) ...

  2. LOJ#565. 「LibreOJ Round #10」mathematican 的二进制 分治,FFT,概率期望

    原文链接www.cnblogs.com/zhouzhendong/p/LOJ565.html 前言 标算真是优美可惜这题直接暴力FFT算一算就solved了. 题解 首先,假装没有进位,考虑解决这个问 ...

  3. 【LOJ565】【LibreOJ Round #10】mathematican 的二进制 DP 分治FFT

    题目大意 有一个无限长的二进制串,初始时它的每一位都为 \(0\).现在有 \(m\) 个操作,其中第 \(i\) 个操作是将这个二进制串的数值加上 \(2^{a_i}\).我们称每次操作的代价是这次 ...

  4. [LOJ#531]「LibreOJ β Round #5」游戏

    [LOJ#531]「LibreOJ β Round #5」游戏 试题描述 LCR 三分钟就解决了问题,她自信地输入了结果-- > -- 正在检查程序 -- > -- 检查通过,正在评估智商 ...

  5. [LOJ#530]「LibreOJ β Round #5」最小倍数

    [LOJ#530]「LibreOJ β Round #5」最小倍数 试题描述 第二天,LCR 终于启动了备份存储器,准备上传数据时,却没有找到熟悉的文件资源,取而代之的是而屏幕上显示的一段话: 您的文 ...

  6. [LOJ#516]「LibreOJ β Round #2」DP 一般看规律

    [LOJ#516]「LibreOJ β Round #2」DP 一般看规律 试题描述 给定一个长度为 \(n\) 的序列 \(a\),一共有 \(m\) 个操作. 每次操作的内容为:给定 \(x,y\ ...

  7. [LOJ#515]「LibreOJ β Round #2」贪心只能过样例

    [LOJ#515]「LibreOJ β Round #2」贪心只能过样例 试题描述 一共有 \(n\) 个数,第 \(i\) 个数 \(x_i\) 可以取 \([a_i , b_i]\) 中任意值. ...

  8. [LOJ#525]「LibreOJ β Round #4」多项式

    [LOJ#525]「LibreOJ β Round #4」多项式 试题描述 给定一个正整数 k,你需要寻找一个系数均为 0 到 k−1 之间的非零多项式 f(x),满足对于任意整数 x 均有 f(x) ...

  9. [LOJ#526]「LibreOJ β Round #4」子集

    [LOJ#526]「LibreOJ β Round #4」子集 试题描述 qmqmqm有一个长为 n 的数列 a1,a2,……,an,你需要选择集合{1,2,……,n}的一个子集,使得这个子集中任意两 ...

随机推荐

  1. eclipse netbeans 代码模板

    eclipse  代码模板  插入slf4j ${:import(org.slf4j.Logger,org.slf4j.LoggerFactory)} private static final Log ...

  2. Win10安装Mongodb,并配置成服务

    好吧,今天突然发现新买的surface上没有安装mongodb,然后想着安装一下,顺便记录一下,虽说安装过程很简单 一:下载安装,然后拷贝到C盘根目录,这个就不多说了,比QQ都简单. 二:把bin文件 ...

  3. Qt的QPixmap半透明

    QPixmap pix1(":/PixmapTest/Resources/Chrysanthemum.jpg"); QPixmap temp(pix1.size()); temp. ...

  4. query.validate.js使用说明+中文API

    转自:http://www.cnblogs.com/hejunrex/archive/2011/11/17/2252193.html 看到一篇好的文章不容易,记录下来以防丢失! 官网地址:http:/ ...

  5. 基于Xcode5的本地化

    一.程序名国际化   1.首先添加应用对多语言支持的国际化文件   点击工程根目录,然后选择PROJECT下的项目,然后选择Info选项卡,在底部可以看到Localizations,点击“+”号,可以 ...

  6. 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量

    建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...

  7. 编写高质量代码改善C#程序的157个建议——建议134:有条件地使用前缀

    建议134:有条件地使用前缀 在.NET的设计规范中,不建议使用前缀.但是,即便是微软自己依然广泛的使用这前缀. 最典型的前缀是m_,这种命名一方面是考虑到历史沿革中的习惯问题,另一方面也许我们确实有 ...

  8. Linq转换操作之ToArray,ToList,ToDictionary源码分析

    Linq转换操作之ToArray,ToList,ToDictionary源码分析 一:linq中的转换运算符 1. ToArray 我们经常用在linq查询上吧. linq只能运用在IEnumerab ...

  9. 【转载】UML类图几种关系的总结

    因为有的时候很久不弄UML图,老是忘记几个常见的连接线的意思,这篇完全说转载:UML类图几种关系的总结 在UML类图中,常见的有以下几种关系: 泛化(Generalization),  实现(Real ...

  10. C#中使用Redis学习二 在.NET4.5中使用redis hash操作

    上一篇>> 摘要 上一篇讲述了安装redis客户端和服务器端,也大体地介绍了一下redis.本篇着重讲解.NET4.0 和 .NET4.5中如何使用redis和C# redis操作哈希表. ...