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

时间:2022-06-01 17:57:07

设第i个人在t时刻生/死为(x,0/1,t),然后显然能够连上(x,0,t)->(x,0,t-1),(x,1,t)->(x,1,t+1),然后对于每个限制,用朴素的2-SAT连边即可。

但这样的点数达到了O(nT),其实有一种方法可以只把限制的边连接建图,点数为4m,这样可能会被卡常。

有没有更优秀的做法?当然还是有的。对于2-SAT中的边(x,y),若y在2-SAT中无出边,则x->y与x->y的后继等价,于是点数可以控制在2n+2m。然后很容易发现,生、死状态的图均为拓扑图,而只有生->死的边没有死->生的边,所以原图是拓扑图所以我们的问题变成了对于每一个(x,0,T+1)求出它能够到达的所有(y,1,T+1)的状态数。于是可以topsort+bitset优化,为了能不爆内存,可以分批topsort,每次104个点左右是最好的。

[JSOI2019]精准预测(2-SAT+拓扑排序+bitset)[JSOI2019]精准预测(2-SAT+拓扑排序+bitset)
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+7,M=3e5+7;
struct node{int tp,t,x,y;};
int T,n,m,sum[N],ans[N],in[M],q[M],vis[N];
bitset<10000>tmp,b[M];
vector<int>G[M],vec[N];
vector<node>now;
void topsort(int L,int R)
{
    int qs=0,qe=0;
    for(int i=1;i<=2*sum[n];i++)for(int j=0;j<G[i].size();j++)in[G[i][j]]++;
    for(int i=1;i<=2*sum[n];i++)if(!in[i])q[qe++]=i;
    while(qs<qe)
    {
        int u=q[qs++];
        for(int i=0;i<G[u].size();i++)
        {
            b[G[u][i]]|=b[u];
            if(!--in[G[u][i]])q[qe++]=G[u][i];
        }
    }
    tmp.reset();
    for(int i=L;i<=R;i++)if(b[sum[i]][i-L])vis[i]=1,tmp.set(i-L);
    for(int i=1;i<=n;i++)if(!vis[i])ans[i]+=(b[sum[i]]|tmp).count();
}
int main()
{
    scanf("%d%d%d",&T,&n,&m);
    for(int i=1;i<=m;i++)
    {
        node u;scanf("%d%d%d%d",&u.tp,&u.t,&u.x,&u.y),u.tp^=1;
        now.push_back(u),vec[u.x].push_back(u.t);
    }
    for(int i=1;i<=n;i++)
    {
        sort(vec[i].begin(),vec[i].end());
        vec[i].push_back(T+1);
        int t=unique(vec[i].begin(),vec[i].end())-vec[i].begin();
        sum[i]=sum[i-1]+t,vec[i].resize(t);
    }
    for(int i=0;i<now.size();i++)
    {
        int x=now[i].x,y=now[i].y,t=now[i].t;
        int p=lower_bound(vec[x].begin(),vec[x].end(),t)-vec[x].begin()+sum[x-1]+1;
        int q=lower_bound(vec[y].begin(),vec[y].end(),now[i].tp+t)-vec[y].begin()+sum[y-1]+1;
        if(now[i].tp)G[q+sum[n]].push_back(p+sum[n]),G[p].push_back(q);
        else G[q+sum[n]].push_back(p),G[p+sum[n]].push_back(q);
    }
    for(int u=1;u<=n;u++)
    for(int i=sum[u-1]+1;i<sum[u];i++)
    G[i].push_back(i+1),G[i+1+sum[n]].push_back(i+sum[n]);
    for(int i=1;i<=n;i+=10000)
    {
        for(int j=1;j<=2*sum[n];j++)b[j].reset();
        for(int j=0;i+j<=n&&j<10000;j++)b[sum[i+j]+sum[n]].set(j);
        topsort(i,min(n,i+9999));
    }
    for(int i=1;i<=n;i++)printf("%d ",vis[i]?0:n-ans[i]-1);
}
View Code