zoukankan      html  css  js  c++  java
  • [LOJ 3101] [Luogu 5332] [JSOI2019]精准预测(2-SAT+拓扑排序+bitset)

    [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
    ",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(' ');
    	}
    }
    
    
  • 相关阅读:
    由于 UNION ALL Chinese_PRC_CI_AS”之间的排序规则冲突,值的排序规则未经解析
    列表框清屏/CListBox清空
    修改CFileDialog的标题
    An Edge-Guided Image Interpolation Algorithm via Directional Filtering and Data Fusion【翻译】
    CDC和HDC的区别与转换
    把图片存/取至数据库
    将图片保存到数据库中、从数据库中读取图片、显示图片、打印图片
    使用ADO实现BLOB数据的存取 -- ADO开发实践之二
    MFC存储图片到SQL Server数据库
    使用ADO GetChunk/AppendChunk 数据库存取二进制文件图象
  • 原文地址:https://www.cnblogs.com/birchtree/p/11331600.html
Copyright © 2011-2022 走看看