[LOJ 3101] [Luogu 5332] [JSOI2019]精准预测(2-SAT+拓扑排序+bitset)

题面

题面较长,略

分析

首先,发现火星人只有死和活两种状态,考虑2-SAT

建图

对于每个火星人,把它按时间和状态拆点,\((i,t,0/1)\)代表第i个火星人在t时刻,0代表活,1代表死。然后按如下方法对每个火星人连边。

1.\((i,t+1,0) → (i,t,0)\),人死了不能复活,所以一个火星人t+1时刻活着,t时刻也一定活着

2.\((i,t,1) → (i,t+1,1)\),同理一个火星人t时刻死了,t+1时刻也一定死了

对于限制\(0 \ t\ x\ y\),连边$(x,t,1) →(y,t+1,1) $

顺便把逆否命题连上$(y,t+1,0) →(x,t,0) $

对于限制\(1 \ t\ x\ y\),连边$(x,t,0) →(y,t,1) $,

顺便把逆否命题连上 $(y,t,0) →(x,t,1) $

这样连边的点数是\(O(nT)\)级别,空间和时间复杂度都超出限制。实际上,我们发现只有在询问里提到的时间点和T+1时间点有意义,这样就可以减少点数,把总点数优化到\(2m+2n\)级别。具体做法是对每个人开一个vector,存储跟这个人有关的时刻。然后在vector里按顺序给点编号。如第1个人vector里有3个元素,编号为1,2,3,第2个人vector里有2个元素,编号为4,5.对于表示活的点i,它对应的死的点编号为(i+总点数)

计算答案

对于每个人i来说,同时与它生还的人最多有n-1个。于是我们把问题反过来考虑,计算若i生还,则确定一定死亡的人数cnt,n-1-cnt即为答案。那么问题就转化成了选择\((i,T,0)\)会有多少个\(j\)使得\((j,T+1,1)\)被选择。在图上的意义是有多少个\((j,T+1,1)\)在\((i,T,0)\)的后继节点中。

容易发现,我们只会从活向死连边,或者活、死两种状态内部连边。而活、死两种状态的内部由于存在时间差,因此一定无环,也就是说我们连出来的图一定是一个DAG,2-SAT一定有解。从图的意义来讲也是这样,因为一开始所有人都死了显然是一个满足条件的解。

那么直接在DAG上按拓扑序的逆序dp即可。用bitset来优化,第x个点的bitset的第k位为1,表示它的后继节点中有节点k。

注意处理一种特殊情况,把自己活能推出自己死的矛盾点记录下来。它的最终答案一定为0,而对于其他的点,无论这些矛盾点在不在它的后继节点中,都要把这些点的数量从n-1里面减去。

内存优化

直接开2(m+n)个大小为n的bitset是会MLE的,我们每一次取maxb个点做拓扑,编号映射到[1,maxb]内,然后累加答案。取maxb=10000时较优

代码

#pragma GCC optimize(3)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<queue>
#include<ctime>
#include<bitset>
#define maxn 50000
#define maxm 100000
#define maxb 10100
using namespace std;
inline void qread(int &x) {
x=0;
int sign=1;
char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') sign=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
x=x*sign;
}
inline void qprint(int x) {
if(x<0) {
putchar('-');
qprint(-x);
} else if(x==0) {
putchar('0');
return;
} else {
if(x>=10) qprint(x/10);
putchar('0'+x%10);
}
} int T,n,m;
struct pred{
int op;
int t;
int x;
int y;
}q[maxm*4+5]; int ind[maxm*4+5];//入度,拓扑排序用
vector<int>E[maxm*4+5];
void add_edge(int u,int v){
// printf("db:%d %d\n",u,v);
E[u].push_back(v);
ind[v]++;
}
vector<int>tim[maxn+5];//存储预测里与第i个人有关的时间
int idl[maxn+5];//第i个人不同时间点的编号的最小值
int idr[maxn+5];//第i个人不同时间点的编号的最大值 int res[maxn+5];
bitset<maxb+5>bin[maxm*4+5];//为了不MLE,每一次取maxb个点做拓扑,编号映射到[1,maxb]内
bitset<maxb+5>tmp;
int cnt=0;
int seq[maxm*4+5];//拓扑序
void topo_sort(){
queue<int>q;
for(int i=1;i<=idr[n]*2;i++){
if(ind[i]==0) q.push(i);
}
while(!q.empty()){
int x=q.front();
seq[++cnt]=x;
q.pop();
for(int y : E[x]){
ind[y]--;
if(ind[y]==0) q.push(y);
}
}
}
void calc(int l,int r){
for(int i=cnt;i>=1;i--){
int x=seq[i];
for(int y : E[x]){
bin[x]|=bin[y];
//按拓扑序的逆序做
}
}
tmp.reset();//每次tmp都要初始化
for(int i=l;i<=r;i++){
if(bin[idr[i]][i-l]){
//把自己活能推出自己死的点x记录下来
res[i]=0;//x的贡献不考虑
tmp.set(i-l);
}
}
for(int i=1;i<=n;i++){//记得从1~n开始更新
if(res[i]){
res[i]-=(tmp|bin[idr[i]]).count();
// 去掉自己活能推出自己死的点,所以要|tmp
}
}
}
int main(){
qread(T);
qread(n);
qread(m);
for(int i=1;i<=m;i++){
qread(q[i].op);
qread(q[i].t);
qread(q[i].x);
qread(q[i].y);
tim[q[i].x].push_back(q[i].t);
}
for(int i=1;i<=n;i++) tim[i].push_back(T+1);
for(int i=1;i<=n;i++){
sort(tim[i].begin(),tim[i].end());
tim[i].resize(unique(tim[i].begin(),tim[i].end())-tim[i].begin());
idl[i]=idr[i-1]+1;
idr[i]=idl[i]+tim[i].size()-1;
}
//1~idr[n]为活点
//idr[n]+1~idr[n]*2为死点
for(int i=1;i<=n;i++){
for(int j=1;j<(int)tim[i].size();j++){
int a=idl[i]+j-1;
add_edge(a+1,a);//(i,t+1,0)->(i,t,0)
add_edge(a+idr[n],a+1+idr[n]);//(i,t,1)->(i,t+1,1)
}
}
for(int i=1;i<=m;i++){
int x=q[i].x;
int y=q[i].y;
int t=q[i].t;
if(q[i].op==0){
int a=lower_bound(tim[x].begin(),tim[x].end(),t)-tim[x].begin()+idl[x];
int b=lower_bound(tim[y].begin(),tim[y].end(),t+1)-tim[y].begin()+idl[y];
add_edge(a+idr[n],b+idr[n]);//(x,t,1)->(y,t+1,1)
add_edge(b,a); //(y,t+1,0)->(x,t,0)
}else{
int a=lower_bound(tim[x].begin(),tim[x].end(),t)-tim[x].begin()+idl[x];
int b=lower_bound(tim[y].begin(),tim[y].end(),t)-tim[y].begin()+idl[y];
add_edge(a,b+idr[n]);//(x,t,0)->(y,t,1)
add_edge(b,a+idr[n]); //(y,t,0)->(x,t,1)
}
}
topo_sort();
for(int i=1;i<=n;i++) res[i]=n-1;//初始的时候一共有n-1个人,等下再减掉死的
for(int i=1;i<=n;i+=maxb){
for(int j=1;j<=2*idr[n];j++){
bin[j].reset();
}
int l=i,r=min(n,i+maxb-1);
for(int j=l;j<=r;j++) bin[idr[j]+idr[n]].set(j-l);//初始化,把死点赋值成1
calc(l,r);
}
for(int i=1;i<=n;i++){
qprint(res[i]);
putchar(' ');
}
}

[LOJ 3101] [Luogu 5332] [JSOI2019]精准预测(2-SAT+拓扑排序+bitset)的更多相关文章

  1. [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连边即可. 但 ...

  2. NOIP 车站分级 (luogu 1983 & codevs 3294 & vijos 1851) - 拓扑排序 - bitset

    描述 一条单向的铁路线上,依次有编号为 1, 2, ..., n 的 n 个火车站.每个火车站都有一个级别,最低为 1 级.现有若干趟车次在这条线路上行驶,每一趟都满足如下要求:如果这趟车次停靠了火车 ...

  3. [JSOI2019]精准预测

    题目 这么明显的限制条件显然是\(\text{2-sat}\) 考虑按照时间拆点,\((0/1,x,t)\)表示\(x\)个人在时间\(t\)是生/死 有一些显然的连边 \[(0,x,t+1)-> ...

  4. 洛谷 P5332 - [JSOI2019]精准预测(2-SAT+bitset+分块处理)

    洛谷题面传送门 七月份(7.31)做的题了,题解到现在才补,不愧是 tzc 首先不难发现题目中涉及的变量都是布尔型变量,因此可以考虑 2-SAT,具体来说,我们将每个人在每个时刻的可能的状态表示出来. ...

  5. 洛谷 P3244 / loj 2115 [HNOI2015] 落忆枫音 题解【拓扑排序】【组合】【逆元】

    组合计数的一道好题.什么非主流题目 题目背景 (背景冗长请到题目页面查看) 题目描述 不妨假设枫叶上有 \(n​\) 个穴位,穴位的编号为 \(1\sim n​\).有若干条有向的脉络连接着这些穴位. ...

  6. [luogu]P4316 绿豆蛙的归宿(拓扑排序,期望)

    P4316 绿豆蛙的归宿 题目背景 随着新版百度空间的上线,Blog宠物绿豆蛙完成了它的使命,去寻找它新的归宿. 题目描述 给出一个有向无环图,起点为1终点为N,每条边都有一个长度,并且从起点出发能够 ...

  7. 【JSOI2019】精准预测(2-SAT & bitset)

    Description 现有一台预测机,可以预测当前 \(n\) 个人在 \(T\) 个时刻内的生死关系.关系有两种: \(\texttt{0 t x y}\):如果 \(t\) 时刻 \(x\) 死 ...

  8. 【LOJ】#3101. 「JSOI2019」精准预测

    LOJ#3101. 「JSOI2019」精准预测 设0是生,1是死,按2-sat连边那么第一种情况是\((t,x,1) \rightarrow (t + 1,y,1)\),\((t + 1,y, 0) ...

  9. LOJ #2116 Luogu P3241「HNOI2015」开店

    好久没写数据结构了 来补一发 果然写的时候思路极其混乱.... LOJ #2116 Luogu P3241 题意 $ Q$次询问,求树上点的颜色在$ [L,R]$中的所有点到询问点的距离 强制在线 询 ...

随机推荐

  1. Flutter SDK安装(windows)

    Flutter集成了Dart,因此不需要单独安装dart-sdk.Flutter的SDK可以从官网下载:https://flutter.io/sdk-archive/#windows 在Flutter ...

  2. 为何使用Shell脚本

    为何使用Shell脚本 分类: linux shell脚本学习2012-09-12 17:18 78人阅读 评论(0) 收藏 举报 shell脚本任务工作         s h e l l 脚本在处 ...

  3. 用电脑Python控制Arduino

    python指令: import serial #导入串口通讯库 import time ser=serial.Serial("com4",9600,timeout=1) demo ...

  4. [CF1004E] Sonya and Ice-cream

    问题描述 Sonya likes ice cream very much. She eats it even during programming competitions. That is why ...

  5. layui 表格设置td的宽度

    layui 表格设置td的宽度, td{ min-width: 150px; max-width: 200px; } 超出长度隐藏 overflow: hidden; text-overflow: e ...

  6. linux运维、架构之路-PHP编译常见报错及解决方法

    1. configure: error: xslt-config not found. Please reinstall the libxslt >= 1.1.0 distribution 复制 ...

  7. web上传大文件(>4G)有什么解决方案?

    众所皆知,web上传大文件,一直是一个痛.上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的. 本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路. 实现文件夹 ...

  8. 修改select的默认样式

    在我们用select的时候,通常因为他的默认样式比较丑而用自己样式,那首先要去掉他的默认样式 去掉select的边框和点击时的蓝色边框 select{border: none;outline: non ...

  9. HDU 6438 Buy and Resell

    高卖低买,可以交易多次 维护一个优先队列,贪心 相当于每天卖出 用当前元素减优先队列最小得到收益 用0/卖出,1/买入标志是否真实进行了交易,记录次数 #include<bits/stdc++. ...

  10. Oracle--SQL程序优化案例一

    下面是存储过程的一部分程序: PROCEDURE SAP_MAN_ROUTING_SO (CITEM_ID    VARCHAR2,                                 C ...