[JSOI2019]精准预测
题目大意
现在有 \(n\) 个人和 \(m\) 条限制,对于每条限制,包含一个 \(c,t,x,y\)。总时间是 \(T\)。
若 \(c=0\),则表示如果在 \(t\) 时刻第 \(x\) 个人死了,那么在 \(t+1\) 时刻第 \(y\) 个人也死了。
若 \(c=1\),则表示如果在 \(t\) 时刻第 \(x\) 个人活着,那么在 \(t\) 时刻第 \(y\) 个人就死了。
现在你需要对每个人求出在 \(T+1\) 可能与它同时活着的人的个数。如果这个人在 \(T+1\) 一定死了,那么输出 0
。
Solution
首先考虑一个暴力,这是一个非常显然的 2-SAT 问题。我们把每个人分成 \(T+1\) 个,对于二元组 \((x,t)\) 表示第 \(x\) 个人的 \(t\) 时刻。那么对于每个二元组,只有两种情况——活着或者死了,并且是相斥的两种状态。
考虑对一些限制连边。
我们用 \((x,t,0)\) 表示 \((x,t)\) 的死亡状态点,\((x,t,1)\) 表示 \((x,t)\) 的存活状态点。那么有以下一些连边。
- \((x,t,0)\to (x,t+1,0)\),表示如果 \(t\) 时刻 \(x\) 已经挂了,那么 \(t+1\) 一定挂了。
- \((x,t,1)\to (x,t-1,1)\),同理,表示 \(t\) 时刻 \(x\) 活着意味着 \(t-1\) 也一定活着。
- 对于题目中第一种限制,有 \((x,t,0)\to (y,t+1,0)\) 和逆否命题 \((y,t+1,1)\to (x,t,1)\)。
- 对于题目中第二种限制,有 \((x,t,1)\to (y,t,0)\) 和逆否命题 \((y,t,1)\to (x,t,0)\)。
首先明确不需要 Tarjan,因为容易看出已经是 DAG 了。
然后考虑题目要求什么。实际上是对每个点 \(x\),从 \((x,T+1,1)\) 能到达多少个 \((y,T+1,0)\),然后 \(n-cnt-1\) 就是这个点的答案。什么意思?就是假设 \(x\) 是活着的,那么能得到一些在 \(T+1\) 必然死掉的人,然后用总人数减去这个人数再刨去自己就是可能与 \(x\) 同时存活的人数了。
然后优化这个残忍的暴力,考虑上面的光是建图就是 \(O(nT)\) 了,非常不靠谱。那考虑把一些没有用的点杀了,首先一点就是没有在询问中出现的点,一定是没有用的。于是考虑对每个点 \(x\) 开一个 vector 存它出现过的时刻,然后建图的时候只存这几个点就好了。
这样点数是 \(O(2m+n)\) 的,其实仔细一想可以更优,询问中出现了 \(x\) 和 \(y\),其实只要 \(x\) 存 \(t\) 就行了,\(y\) 没有必要。因为如果 \(y\) 后续没有作为 \(x\) 出现在询问中的话,对最后结果没有影响,可以直接用大于它的最小出现的那个节点代替它。是一样的。(不过这没有关系,没看懂也不要紧)
这样点数就优化下来了,同时边数也下来了。
然后考虑空间的问题,发现如果对每个节点暴力跑肯定是会炸的,那么容易想到用 bitset 优化这个过程。可是如果对每个节点开一个大小为 \(n\) 的 bitset 我是想都不敢想。所以这里用到一个小 trick——平衡复杂度,既然两边一边是空间大,一边是时间大,那么何不平衡一波。我们考虑把 \(n\) 个人分批处理,每次处理 \(S\) 个人,这样空间复杂度就成了 \(O(\dfrac{nS}{8})\),卡卡可以过去,时间就是 \(O(\dfrac{n(n+m)}{S})\),\(S=10000\) 可以过去。
Code
#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
#define pb push_back
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define pll pair<ll,ll>
#define rep(i,j,k) for(int i=(j);i<=(k);i++)
#define per(i,j,k) for(int i=(j);i>=(k);i--)
using namespace std;
const int MAXN=1e6+10;
const int MAXM=300010;
vector<int> used[MAXN],e[MAXM];
map<int,int> mp[MAXN][2];
bitset<10010> dp[MAXM],tmp;
int tot=0,deg[MAXM];
void sprd(){
queue<int> q;
rep(i,1,tot) for(int s:e[i]) deg[s]++;
rep(i,1,tot) if(!deg[i]) q.push(i);
while(!q.empty()){
int x=q.front();q.pop();
for(int s:e[x]){
dp[s]|=dp[x];
if(--deg[s]==0) q.push(s);
}
}
}
struct Query{
int c,t,x,y;
void input(){
cin>>c>>t>>x>>y;
used[x].pb(t);
}
}q[MAXN];
int ans[MAXN];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T,n,m;
cin>>T>>n>>m;
rep(i,1,m) q[i].input();
rep(i,1,n) used[i].pb(T+1),sort(used[i].begin(),used[i].end()),
used[i].erase(unique(used[i].begin(),used[i].end()),used[i].end());
rep(i,1,n){
for(int t:used[i]) mp[i][0][t]=++tot,mp[i][1][t]=++tot;
rep(j,0,(int)used[i].size()-1){
if(j) e[mp[i][1][used[i][j-1]]].pb(mp[i][1][used[i][j]]);
if(j<(int)used[i].size()-1)
e[mp[i][0][used[i][j+1]]].pb(mp[i][0][used[i][j]]);
}
}
rep(i,1,m){
if(q[i].c==0){
int v=upper_bound(used[q[i].y].begin(),used[q[i].y].end(),q[i].t)-used[q[i].y].begin();
int to=used[q[i].y][v];
e[mp[q[i].y][0][to]].pb(mp[q[i].x][0][q[i].t]);
e[mp[q[i].x][1][q[i].t]].pb(mp[q[i].y][1][to]);
// cerr<<mp[q[i].y][0][to]<<' '<<mp[q[i].x][0][q[i].t]<<'\n';
// cerr<<mp[q[i].x][1][q[i].t]<<' '<<mp[q[i].y][1][to]<<'\n';
}else{
int v=lower_bound(used[q[i].y].begin(),used[q[i].y].end(),q[i].t)-used[q[i].y].begin();
int to=used[q[i].y][v];
e[mp[q[i].y][0][to]].pb(mp[q[i].x][1][q[i].t]);
e[mp[q[i].x][0][q[i].t]].pb(mp[q[i].y][1][to]);
// cerr<<mp[q[i].y][0][to]<<' '<<mp[q[i].x][1][q[i].t]<<'\n';
// cerr<<mp[q[i].x][0][q[i].t]<<' '<<mp[q[i].y][1][to]<<'\n';
}
}
rep(i,1,n) ans[i]=n-1;
for(int l=1,r;l<=n;){
r=min(l+9999,n);
rep(i,1,tot) dp[i].reset();
rep(i,l,r) dp[mp[i][0][T+1]][i-l+1]=1;
sprd();tmp.reset();
rep(i,l,r) if(dp[mp[i][1][T+1]][i-l+1]) ans[i]=0,tmp[i-l+1]=1;
rep(i,1,n) dp[mp[i][1][T+1]]|=tmp;
rep(i,1,n) if(ans[i]) ans[i]-=dp[mp[i][1][T+1]].count();
l=r+1;
}
rep(i,1,n) cout<<ans[i]<<' ';
return 0;
}