【JSOI2019】精准预测(2-SAT & bitset)
Description
现有一台预测机,可以预测当前 \(n\) 个人在 \(T\) 个时刻内的生死关系。关系有两种:
- \(\texttt{0 t x y}\):如果 \(t\) 时刻 \(x\) 死了,那么 \(y\) 在第 \(t+1\) 时刻也会死亡。
- \(\texttt{1 t x y}\):如果 \(t\) 时刻 \(x\) 活着,那么 \(y\) 在 \(t\) 时刻就会死亡。
这样的关系共有 \(m\) 条。现在你需要在不违背这些关系的前提下,计算对于每一个人 \(i\),可能可以与 \(i\) 活到第 \(T+1\) 时刻的有多少人。形式化的讲,设 \(\text{Live}(i, j)\) 表示是否 \(i, j\) 可以同时存活,如果可以则为 \(1\),反之为 \(0\)。那么你需要对于每一个 \(i\in[1, n]\),计算
\]
的值。
Hint
\(1\le n\le 5\times 10^4, 1\le m\le 10^5, 1\le T\le 10^6\)。
Solution
考虑用 2-SAT 思想将生死关系转化为图:记 \((x,t), \neg (x,t)\) 分别表示 \(x\) 这个人在时刻 \(t\) 的 活 / 死 两个状态(图上的顶点),\(a\to b\) 表示一条从 \(a\) 到 \(b\) 的有向边。
- 对于一个人 \(i\),如果 \(t\) 时刻他死了,那么显然 \(t+1\) 时刻也是死的。同理,如果 \(t+1\) 时刻是活的,那么 \(t\) 时刻一定也是活的。连边 \(\neg(i, t)\to \neg(i,t+1),(i,t+1)\to(i,t)\)。
- 对于一个关系 \(\texttt{0 t x y}\),显然连边 \(\neg(x, t)\to \neg(y,t+1)\)。注意如果 \(t+1\) 时 \(y\) 没死,那么说明这个条件没有生效,在 \(t\) 时刻 \(x\) 是活的,所以 \((y,t+1)\to (x,t)\)。
- 对于一个关系 \(\texttt{1 t x y}\),显然连边 \((x, t)\to \neg(y,t)\)。同上反向考虑有 \((y, t)\to \neg(x,t)\)。
这样我们就把图建好了。然而我们发现 闷声发大财 点数是 \(O(n\times T)\) 的。其实这个还好办,我们只要对每个点 \(x\),把它在条件中出现过的 \(t\) 作为点开出来,其他 没有出现在条件中的就是没用的点。具体来讲,对每个点开一个 set
存储有用的时刻(包括 \(T+1\)),然后用一个 map
存一下编号即可。这样一来,点和边的规模为 \(O(m+n)\),可以承受。
那么,如何用这张图求解答案呢?为了方便,设 \(\text{alive}(x) = (x, T+1),\text{dead}(x) = \neg(x, T+1)\),分别表示最后结束时 \(x\) 这个人生死对应的两个状态。
我们考虑一条有向边 \(a\to b\) 的实际意义:如果 \(a\) 为真,那么可以推出 \(b\) 也为真。如果说存在一个 \(y\ne x\) 使得 \(\text{alive}(x)\) 可以到达 \(\text{dead}(y)\),那么表明 \(x\) 活着的话 \(y\) 就会死,这样两个人显然无法同时活着。
如果我们对于每个 \(x\) 都求出满足上述条件的 \(y\) 集合的大小,那么 \(n\) 减去这个集合的大小再减一(减去自己)即为答案。
在实际实现时,我们枚举 \(i\in [1, n]\) 并从 \(\text{alive}(i)\) 开始 DFS,搜出这个 \(\text{alive}(i)\) 可以搜到那些 \(\text{dead}(j)\),最后我们得到了一个由所有 \(\text{alive}(i)\) 可及的 \(\text{dead}(j)\) 组成的一个集合,然后答案就比较显然了。
但这样的复杂度是平方级别,我们考虑优化。发现对于 DFS 树上的一个结点 \(x\) 以及其子节点 \(y_1, y_2, \cdots y_k\)。若记 \(f(i)\) 为 \(i\) 可以到达的点集,那么 \(f(x) = \left(\cup_{i=1}^k f(y_i)\right)\cup\{x\}\)。不难发现这个 \(\cup\) 其实是可以用 bitset
的位运算 进行优化的,复杂度降为 \(O(\frac 1 \omega n(n+m))\)。注意,这里 \(f\) 也有记忆化的作用,使得一个点不被搜两次。既然是记忆化,那么相当于在 DAG 上 dp。具体为什么是 DAG,发现只会有生 \(\to\) 死的边而没有死 \(\to\) 生的,并且死 \(\to\) 死的边存在时间先后顺序,不会存在 SCC。
难受的是 \(\frac 1 \omega n(n+m)\) 大小的 bitset
直接炸空间了,于是我们使用神奇技巧强行卡进去。
考虑把 \(n\) 个人分成若干块 分批处理,一块 \(B\) 个,然后空间常数就小下来了。实测 \(B\approx 2\times 10^4\) 时可过。
实现上一个细节:特别标记 必死点,即 \(\text{alive}(x)\) 可以到达 \(\text{dead}(x)\)。这种点单独输出 \(0\)。
Code
以下代码部分参考了 这篇博客。
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : JSOI2019 精准预测
*/
#include <algorithm>
#include <bitset>
#include <iostream>
#include <set>
#include <map>
#include <vector>
using namespace std;
const int N = 5e4 + 5;
const int MaxT = 1e6 + 5;
const int M = 1e5 + 5;
const int B = 1e4;
const int V = (M + N) << 1;
int n, m, T;
int type[M], t[M], x[M], y[M];
int ans[N];
set<int> vaild[N];
map<int, int> idx[N][2];
int total = 0;
vector<int> adj[V]; // graph
void link(int u, int v) { adj[u].emplace_back(v); }
int l[N], d[N], bel[V]; // live, dead
bitset<N> dead;
bitset<V> vis;
bitset<B> statu[V]; // f
void dfs(int x, int p, int q) {
if (vis[x]) return; vis.set(x);
statu[x].reset();
if (p <= bel[x] && bel[x] <= q) statu[x].set(bel[x] - p);
for (auto y : adj[x]) dfs(y, p, q), statu[x] |= statu[y];
}
signed main() {
ios::sync_with_stdio(false), cin >> T >> n >> m;
for (int i = 1; i <= m; i++) cin >> type[i] >> t[i] >> x[i] >> y[i];
for (int i = 1; i <= n; i++) vaild[i].insert(T + 1);
for (int i = 1; i <= m; i++) vaild[x[i]].insert(t[i]);
for (int i = 1; i <= n; i++) {
for (auto t : vaild[i])
idx[i][0][t] = ++total, idx[i][1][t] = ++total;
for (auto t = vaild[i].begin(); t != vaild[i].end() && next(t) != vaild[i].end(); t++)
link(idx[i][0][*t], idx[i][0][*next(t)]), link(idx[i][1][*next(t)], idx[i][1][*t]);
}
for (int i = 1; i <= m; i++) {
if (!type[i]) {
int to = *vaild[y[i]].upper_bound(t[i]);
link(idx[x[i]][0][t[i]], idx[y[i]][0][to]);
link(idx[y[i]][1][to], idx[x[i]][1][t[i]]);
} else {
int to = *vaild[y[i]].lower_bound(t[i]);
link(idx[x[i]][1][t[i]], idx[y[i]][0][to]);
link(idx[y[i]][1][to], idx[x[i]][0][t[i]]);
}
}
for (int i = 1; i <= n; i++) l[i] = idx[i][1][T + 1], bel[d[i] = idx[i][0][T + 1]] = i;
for (int p = 1, q; p <= n; p += B) {
q = min(p + B - 1, n), vis.reset();
for (int i = 1; i <= n; i++) dfs(l[i], p, q);
bitset<B> cur;
for (int i = p; i <= q; i++) if (statu[l[i]][i - p]) dead.set(i), cur.set(i - p);
for (int i = 1; i <= n; i++) ans[i] += (q - p + 1) - (cur | statu[l[i]]).count();
}
for (int i = 1; i <= n; i++)
cout << (dead[i] ? 0 : ans[i] - 1) << ' ';
return cout << endl, 0;
}
【JSOI2019】精准预测(2-SAT & bitset)的更多相关文章
- 洛谷 P5332 - [JSOI2019]精准预测(2-SAT+bitset+分块处理)
洛谷题面传送门 七月份(7.31)做的题了,题解到现在才补,不愧是 tzc 首先不难发现题目中涉及的变量都是布尔型变量,因此可以考虑 2-SAT,具体来说,我们将每个人在每个时刻的可能的状态表示出来. ...
- [LOJ 3101] [Luogu 5332] [JSOI2019]精准预测(2-SAT+拓扑排序+bitset)
[LOJ 3101] [Luogu 5332] [JSOI2019]精准预测(2-SAT+拓扑排序+bitset) 题面 题面较长,略 分析 首先,发现火星人只有死和活两种状态,考虑2-SAT 建图 ...
- [JSOI2019]精准预测(2-SAT+拓扑排序+bitset)
设第i个人在t时刻生/死为(x,0/1,t),然后显然能够连上(x,0,t)->(x,0,t-1),(x,1,t)->(x,1,t+1),然后对于每个限制,用朴素的2-SAT连边即可. 但 ...
- [JSOI2019]精准预测
题目 这么明显的限制条件显然是\(\text{2-sat}\) 考虑按照时间拆点,\((0/1,x,t)\)表示\(x\)个人在时间\(t\)是生/死 有一些显然的连边 \[(0,x,t+1)-> ...
- 【LOJ】#3101. 「JSOI2019」精准预测
LOJ#3101. 「JSOI2019」精准预测 设0是生,1是死,按2-sat连边那么第一种情况是\((t,x,1) \rightarrow (t + 1,y,1)\),\((t + 1,y, 0) ...
- AI带你省钱旅游!精准预测民宿房源价格!
作者:韩信子@ShowMeAI 数据分析实战系列:https://www.showmeai.tech/tutorials/40 机器学习实战系列:https://www.showmeai.tech/t ...
- JSOI2019 Round2
JSOI的题质量很高-- 精准预测(2-SAT.拓扑排序.bitset) 不难发现两个条件都可以用经典的2-SAT连边方式连边,考虑如何加入时间的限制.对于第\(x\)个人在\(t\)时刻的状态是生/ ...
- 【天池大数据赛题解析】资金流入流出预测(附Top4答辩ppt)
http://mp.weixin.qq.com/s?__biz=MzA3MDg0MjgxNQ==&mid=208451006&idx=1&sn=532e41cf020a0673 ...
- KDDCUP CTR预测比赛总结
赛题与数据介绍 给定查询和用户信息后预测广告点击率 搜索广告是近年来互联网的主流营收来源之一.在搜索广告背后,一个关键技术就是点击率预测-----pCTR(predict the click-thro ...
随机推荐
- 【Redis】利用 Redis 实现分布式锁
技术背景 首先我们需要先来了解下什么是分布式锁,以及为什么需要分布式锁. 对于这个问题,我们可以简单将锁分为两种--内存级锁以及分布式锁,内存级锁即我们在 Java 中的 synchronized 关 ...
- PID算法终于弄明白原理了,原来就这么简单
看起来PID高大尚,实则我们都是被他的外表所震撼住了.先被别人唬住,后被公式唬住,由于大多数人高数一点都不会或者遗忘,所以再一看公式,简直吓死.了解了很浅的原理后,结果公式看不懂,不懂含义,所以最终没 ...
- a^b(取模运算)
a^b(sdtbu oj 1222) Description 对于任意两个正整数a,b(0 <= a, b < 10000)计算ab各位数字的和的各位数字的和的各位数字的和的各位数字的和. ...
- 有关String的那点事
(1)String str1 = "abc"; System.out.println(str1 == "abc"); 步骤: 1) 栈中开辟一块空间存放引用st ...
- JPA、Hibernate、Spring-Data-Jpa的本质区别
什么是JPA? 全称Java Persistence API,可以通过注解或者XML描述[对象-关系表]之间的映射关系,并将实体对象持久化到数据库中. 为我们提供了: 1)ORM映射元数据:JPA支持 ...
- webpack、node、npm关系
webpack模块打包 webpack为了正常运行, 必须依赖node环境, node环境为了可以正常的执行很多代码,必须其中包含各种依赖的包 npm工具(node packages manager) ...
- 免费|申请谷歌云服务器|msf安装
apt-get install -y wget 参考链接 知乎-免费|申请谷歌云服务器 知乎-免费|申请谷歌云服务器 cnblogs-debian.ubuntu安装metasploit通用方法 谷歌云 ...
- kali 2020.1 更新源,并安装docker
先说一句浙大牛逼!!!装个docker折腾了半天,测了半天只有浙大的更新源能用,完美不报错!清华阿里什么的更新源都是渣渣. deb http://mirrors.zju.edu.cn/kali kal ...
- FL Studio杂项设置页讲解(上)
今天我们来看一下FL Studio通道设置窗口中的杂项设置页面.该页面存在于FL Studio绝大多数的通道中,我们可以通过它来设置一些发生器或者第三方插件的参数,接下来就让我们一起来学习下这些参数的 ...
- 教你调节Boom 3D的3D音效强度,让音乐更带感
Boom 3D的专业3D环绕技术,让用户能全身心地沉浸在立体音效中.无论是聆听音乐,还是观赏电影,立体音效都能为人们带来更加真实的听觉感触. 那么,Boom 3D的3D环绕功能到底能给用户带来怎样的体 ...