zoukankan      html  css  js  c++  java
  • 压位优化

    很多题目都可以使用压位优化,这里介绍一下压位优化的例子。
    压位可以用来优化一些问题。通常可以把时间复杂度/=32。
    先举例子:图的传递闭包。
    先tarjan缩点,变成dag(有向无环图)。
    缩点以后每一个点都能到达强联通分量内的点。
    考虑dp。设(f_x)(x)能够到达的点。
    (f_x|=f_v),其中(v)(x)的出边。
    使用线性rmq那样的优化时间复杂度还可以/=(log_2n)
    按照拓扑序逆序构造答案。
    把点分成大小为(B)的块,有(frac{n}{B})个块。
    处理每个块(2^B)个出边的集合。
    每次遍历(frac{n}{B}),把这些块预处理的结果并在一起即可。
    这样子时间复杂度/=(log_2n)
    这样子可以用于优化uoj76,获得满分。
    还有bitset优化kosaraju。
    由于在tarjan内,每个点会被遍历多次,所以不能用tarjan。
    kosaraju的两次dfs都可以使用bitset优化。
    设标记数组的bitset为vis,每一个点(x)连出的边集为(e_x)
    则每次遍历(e_x)的时候,遍历的是(e_x)&(vis),这样子每个点就只会被遍历一次,时间复杂度/=32;
    jzoj内有一道题要用到这个技巧。
    那道题如果要修改,莫队一下。
    求出图需要(nsqrt{n})时间,每次重新缩点即可。
    再举例子namomo 1012无向图。
    题目中要求两条不相交路径,使得其距离之和最小,显然可以费用流。
    如果现在的节点是(x,y),每条边的边权和容量都是(1),则答案就是(x,y)增广(2)次的费用
    第一次bfs找最短路,第二次由于有边变负,不能bfs了。
    有一个技巧是dij费用流。先跑一遍spfa求出某个节点(任选)到所有节点的距离(d_i),然后把每条边(x,y)的权值变为(d_x-d_y+w(x,y))
    跑完费用流后,把每个点的(d)加上dij跑出来的距离的对应值。
    (d_x-d_y+w(x,y)>=0),移项得到(d_x+w(x,y)>=d_y)
    如果(d_x+w(x,y)<d_y),则(y)的权值还能再被更新。
    所以某点对(s->t)的费用是(d_s-d_t+s->t),最短路变了,但是最短路径不变。符合要求。
    但是跑完以后可能会加入反向边。
    对于每条最短路上的边,显然:
    (d'_x+w'(x,y)=d'_y)
    设势能数组为(h)
    ((d'_x+h_x)-(d'_y+h_y)+w(x,y)=0)
    ((d'_x+h_x)-(d'_y+h_y)=w(y,x))
    只需要把每个点的权值+=(d_i)即可。
    所以每条边的权值都是(>=0)的,可以跑dij。
    这样子可以再(O(n^2log_2n))内计算固定端点的答案,时间复杂度(O(n^4log_2n))
    下面叙述如何把时间复杂度优化到(frac{n^4}{omega})
    由于重新赋值后边的权值只能是(0,1,2),十分特殊,可以使用bitset优化。
    使用(3)个bitset(e_{i,0},e_{i,1},e_{i,2})维护点i权值为(0/1/2)的出边。
    如果bitset的(j)(1),则存在(i,j)这条边。
    (q_i)表示离起点距离为(<=i)的点集。
    我们要求(q_{0...n*2})
    由定义,(q_{i+1})必须要包含(q_i)
    枚举每个(i),由(q_i)更新后面的点。
    枚举权值(1,2),假设现在边权是(w),则(q_{i})可以更新(q_{i+w}),把(q_{i+w})并上(q_{i})
    这相当于批量进行dij的更新操作。
    为了节省时间,我们只需把(q_i-q_{i-1})的所有节点的(e_{v,x})并起来。
    这样子均摊时间复杂度是(O(frac{n^2}{omega}))
    为什么?因为(q_{i-1})的节点可以通过之前的操作用来更新(q_{i},q_{i+1})
    由于(q_i)更新之前会|=(q_{i-1}),所以之前已经更新过(q_{i-1})了,不用考虑(q_{i-1})的影响。
    而且每个点只会更新一次答案。均摊时间复杂度是(O(frac{n^2}{omega}))
    由于前面只使用权值为(1,2)的边更新后面的节点,所以要对于每个(i),把所有(q_i)可以到达的,连了0权边的节点加入(q_i)
    在更新连(0)权边的节点时,使用bitset来遍历每一条出边。
    在实现时,要把(0)权边加入(q_i),再用(1,2)权值的边更新后面的节点。
    由于每一个点最多被遍历(2)次,时间复杂度是(O(2*frac{n^2}{omega}))
    为了维护(e)数组,要维护一个数组(dd)(dd_i)表示重标号以后离原点距离为(i)的点的bitset。
    枚举一个原点(x),设它的距离为(p),枚举它的距离标号-出点的距离标号。
    用这两个信息和(dd)得到(e)数组。
    被卡常的代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define N 110
    #define mo 1000000007
    int T,pw[N*2],n,m,d[N],p[N][N][N],ct[N][N],pp[N],v[N][N],d1[N],dv[N][N],qu[2000000],a,b,oo;
    bitset<N>g[N],q[N*2],e[N][3],bz,tp,bv[N],w[N];
    void bfs(int x,int t){
    	a=1;
    	b=1;
    	qu[a]=x;
    	for(int i=1;i<=n;i++)
    		pp[i]=0;
    	if(t){
    		for(int i=1;i<=n;i++)
    			d1[i]=-1;
    		d1[x]=0;
    	}
    	else{
    		for(int i=1;i<=n;i++)
    			d[i]=-1;
    		d[x]=0;
    	}
    	while(a<=b){
    		int x=qu[a];
    		a++;
    		for(int i=1;i<=n;i++)
    			if(g[x][i]){
    				if(t&&d1[i]==-1){
    					d1[i]=d1[x]+1;
    					pp[i]=x;
    					qu[++b]=i;
    				}
    				else if(!t&&d[i]==-1){
    					d[i]=d[x]+w[x][i];
    					pp[i]=x;
    					qu[++b]=i;
    				}
    			}
    	}
    }
    void bb(int x,int id){
    	a=1;
    	b=1;
    	qu[a]=x;
    	bitset<N>tl;
    	while(a<=b){
    		int x=qu[a];
    		a++;
    		tp[x]=0;
    		q[id][x]=1;
    		int lb=0;
    		tl=tp&e[x][0];
    		while(1){
    			int p=(tl)._Find_next(lb);
    			if(p>n)break;
    			qu[++b]=p;
    			lb=p;
    		}
    	}
    }
    int gt(int x,int y){
    	for(int i=0;i<=2*n;i++)
    		q[i].reset();
    	bz.reset();
    	for(int i=1;i<=n;i++)
    		bz[i]=1;
    	int ans=0,ok=0;
    	for(int i=0;i<=2*n;i++){
    		if(i)q[i]|=q[i-1];
    		else q[i][x]=1;
    		bitset<110>t;
    		if(i)t=q[i]^q[i-1];
    		else t=q[i];
    		tp=bz^q[i];
    		int lb=0;
    		while(1){
    			int p=t._Find_next(lb);
    			if(p>n)break;
    			bb(p,i);
    			lb=p;
    		}
    		if(i)t=q[i]^q[i-1];
    		else t=q[i];
    		lb=0;
    		while(1){
    			int p=t._Find_next(lb);
    			if(p>n)break;
    			if(i+1<=2*n)
    				q[i+1]|=e[p][1];
    			if(i+2<=2*n)
    				q[i+2]|=e[p][2];
    			lb=p;
    		}
    		if(q[i][y]){
    			ans=i;
    			ok=1;
    			break;
    		}
    	}
    	if(!ok)return 0;
    	return (dv[x][y]+ans+dv[x][y]+mo)%mo;
    }
    int main(){
    	pw[0]=1;
    	for(int i=1;i<N*2;i++)
    		pw[i]=1ll*pw[i-1]*233%mo;
    	scanf("%d",&T);
    	int cs=0;
    	while(T--){
    		cs++;
    		scanf("%d%d",&n,&m);
    		for(int i=1;i<=n;i++){
    			g[i].reset();
    			d[i]=0;
    			e[i][0].reset();
    			e[i][1].reset();
    			e[i][2].reset();
    		}
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=n;j++)
    				ct[i][j]=0;
    		for(int i=1;i<=m;i++){
    			int a,b;
    			scanf("%d%d",&a,&b);
    			g[a][b]=1;
    			g[b][a]=1;
    		}
    		bfs(1,1);
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=n;j++)
    				w[i][j]=1;
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=n;j++)
    				if(g[i][j])
    					e[i][w[i][j]][j]=1;
    		for(int i=1;i<=n;i++){
    			bfs(i,0);
    			for(int j=1;j<=n;j++){
    				dv[i][j]=d[j];
    				int x=j;
    				while(x){
    					p[i][j][++ct[i][j]]=x;
    					x=pp[x];
    				}
    			}
    		}
    		int ans=0;
    		for(int i=1;i<=n;i++){
    			for(int j=i+1;j<=n;j++){
    				for(int k=1;k<=n;k++)
    					bv[k].reset();
    				for(int k=1;k<=n;k++)
    					bv[dv[i][k]][k]=1;
    				for(int k=1;k<=n;k++){
    					e[i][0].reset();
    					e[i][1].reset();
    					e[i][2].reset();
    				}
    				for(int k=1;k<=n;k++)
    					for(int l=-1;l<=1;l++)
    						if(dv[i][k]+l<=n&&dv[i][k]+l>=0){
    							e[k][1-l]=g[k]&bv[dv[i][k]+l];
    							e[k][1-l][k]=0;
    						}
    				for(int k=1;k<ct[i][j];k++){
    					int x=p[i][j][k+1],y=p[i][j][k],z1=-1+dv[i][y]-dv[i][x],z2=1+dv[i][x]-dv[i][y];
    					e[y][z1][x]=1;
    					e[x][z2][y]=0;
    				}
    				int t=gt(i,j);
    				ans=(ans+1ll*t*pw[i+j]%mo)%mo;
    			}
    		}
    		printf("Case %d
    ",cs);
    		ans=ans*2%mo;
    		printf("%d
    ",ans);
    	}
    }
    
  • 相关阅读:
    16款值得一用的iPhone线框图模板 (PSD & Sketch)
    设计神器
    {CF812}
    hiho1080(多标记线段树)
    {容斥原理}
    {dp入门}
    {AC自动机}
    CF807
    Trie树
    杂记
  • 原文地址:https://www.cnblogs.com/cszmc2004/p/13454164.html
Copyright © 2011-2022 走看看