[JSOI2019] 精准预测 题解
容易发现题目给出的三大条件(难兄难弟、死神来了、不能复活)都可以用经典 \(2-sat\) 建图解决。假如我们对于每个火星人都拆成 \(T+1\) 份,那点数肯定爆炸,所以考虑对点进行合并,只将在该火星人可能因为外力发生改变或能改变其他火星人生死的时间作为断点(当然,此处不包括不能复活)。
具体连边方式如下:
- 难兄难弟:\(!x_{*-t}\to !y_{(t+1)-*},y_{(t+1)-*}\to x_{*-t}\),\(x,y\) 火星人的断点都为 \(t\)。
- 死神来了:\(x_{*-t}\to !y_{t-*},y_{t-*}\to !x_{*-t}\),\(x\) 火星人断点为 \(t\),\(y\) 火星人断点为 \(t-1\)。
- 不能复活:\(!x_{*-t}\to !x_{(t+1)-*},x_{(t+1)-*}\to x_{*-t}\)。
现在我们建出了 \(2-sat\),那么该如何求 \(\sum\operatorname{Live}(i,k)\) 呢?
考虑 \(2-sat\) 中,\(x\to y\) 的实际含义为:可以根据 \(x\) 推出 \(y\)。那么假如我们可以根据 \(i_{*-(T+1)}\) 推出 \(!k_{*-(T+1)}\),那就说明 \(\operatorname{Live}(i,k)=0\)。这个东西我们可以用 \(tarjan\) 后的拓扑排序 \(+bitset\) 求解。时空复杂度 \(O(\dfrac n\omega(n+m))\),于是炸空间了。
既然炸空间了,我们选择将 \(n\) 个 \(!k_{*-(T+1)}\) 拆成多组,这样就可以减少空间复杂度。
#include<bits/stdc++.h>
using namespace std;
const int N=50005,M=1e5+5,K=5e5+5;
int t,n,m,tot,fs[N],ed[N],ans[N];
int cc[M],tc[M],xc[M],yc[M],r[K],tp;
vector<int>g[K],di[N],d[N],ve[K];
bitset<N/5>de[K],zjy;int idx[K],id;
int dfn[K],low[K],vs[K],st[K],cnt;
unordered_map<int,int>mp[K];
void tarjan(int x){
dfn[x]=low[x]=++id,vs[st[++tp]=x]=1;
for(auto y:g[x]){
if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]);
else if(vs[y]) low[x]=min(low[x],dfn[y]);
}if(dfn[x]!=low[x]) return;cnt++;
while(st[tp+1]!=x) idx[st[tp]]=cnt,vs[st[tp--]]=0;
}int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>t>>n>>m;queue<int>q;
for(int i=1,c;i<=m;i++){
cin>>cc[i]>>tc[i]>>xc[i]>>yc[i];
di[yc[i]].push_back(tc[i]-cc[i]);
di[xc[i]].push_back(tc[i]);
}for(int i=1;i<=n;i++){
di[i].push_back(t+1);int lst=0;
sort(di[i].begin(),di[i].end());
for(auto x:di[i]) if(lst!=x) d[i].push_back(lst=x);
fs[i]=tot+1,ed[i]=(tot+=d[i].size());
}for(int i=1;i<=n;i++) for(int j=fs[i];j<ed[i];j++)
g[j+tot].push_back(j+tot+1),g[j+1].push_back(j);
for(int i=1;i<=m;i++){
int xd=xc[i],yd=yc[i],td=tc[i],cd=cc[i];
int x=fs[xd]+lower_bound(d[xd].begin(),d[xd].end(),td)-d[xd].begin();
int y=fs[yd]+upper_bound(d[yd].begin(),d[yd].end(),td-cd)-d[yd].begin();
if(cd) g[x].push_back(y+tot),g[y].push_back(x+tot);
else g[x+tot].push_back(y+tot),g[y].push_back(x);
}for(int i=1;i<=tot*2;i++) if(!dfn[i]) tarjan(i);
for(int i=1;i<=tot*2;i++) for(auto j:g[i])
if(idx[i]!=idx[j]&&!mp[idx[i]][idx[j]])
ve[idx[j]].push_back(idx[i]),mp[idx[i]][idx[j]]=1;
for(int i=1;i<=n;i++) ans[i]=n-1;
for(int num=0;num*10000+1<=n;num++){
int fst=num*10000+1,end=min(fst+9999,n);
for(int i=fst;i<=end;i++) de[idx[ed[i]+tot]][i-fst+1]=1;
for(int i=1;i<=cnt;i++) if(!(r[i]=mp[i].size())) q.push(i);
while(q.size()){
int x=q.front();q.pop();
for(auto y:ve[x]){
de[y]|=de[x],r[y]--;
if(!r[y]) q.push(y);
}
}for(int i=fst;i<=end;i++) zjy[i-fst+1]=de[idx[ed[i]]][i-fst+1];
for(int i=1;i<=n;i++){
if(fst<=i&&i<=end&&zjy[i-fst+1]) ans[i]=0;
else ans[i]-=(zjy|de[idx[ed[i]]]).count();
}for(int i=1;i<=cnt;i++) de[i].reset();zjy.reset();
}for(int i=1;i<=n;i++) cout<<max(ans[i],0)<<" ";
return 0;
}