zoukankan      html  css  js  c++  java
  • 【瞎口胡】线段树分治

    线段树分治是一种优秀的时间轴分治算法。

    例题 1 Luogu P5787 二分图

    题意

    有一张 \(n\) 个点 \(m\) 条边的无向图,第 \(i\) 条边在 \(x\) 时刻出现 \(y\) 时刻消失,询问时间段 \(1 \sim k\) 这张图是不是二分图。

    时间段 \(x\) 的定义是时刻 \(x-1\)\(x\) 这一段时间。

    \(1 \leq n,k \leq 10^5, m \leq 2 \times 10^5,1 \leq x,y \leq k\)

    题解

    容易观察到,在这道题中,我们要维护的信息易加不易减。线段树分治可以很好地避免这个问题。

    对时间建线段树,一条边的出现和消失就是区间修改。每个节点挂一个 std::vector 就行。

    假设当前要询问的时间是 \(x\)。在线段树上从根节点往下走到 \(x\) 对应的叶节点,并将沿途经过所有节点中存下的边加入图中。

    用扩展域并查集维护。处理一条边 \((u,v)\) 时,在并查集中将点 \((u,v')\)\((v,u')\) 连边。如果某个点 \(i\)\(i'\) 在并查集中连通,则该图不是二分图。这是因为二分图的判定定理:一个图是二分图当且仅当图中不存在奇环。考虑一条边一定从不带 \('\) 的点连向带 \('\) 的点,如果 \(i\)\(i'\) 在并查集中连通,那么一定经过了奇数条边,这样图中就存在一个奇环;反之亦然。

    但我们不能直接进行 \(k\) 次查询:每次查询需要清空数组,复杂度退化为了 \(O(kn)\)。如果使用可撤销并查集就可以解决这一问题。具体来讲,在并查集连边时将这一条边两个端点连边前的 \(fa\) 记录下来,存放在栈中,在退出当前节点时弹栈复原信息即可。同时,我们查询完当前节点的左子树时不退出,而是直接进入右子树查询。

    时间复杂度 \(O(m \log k \log n)\)

    # include <bits/stdc++.h>
    
    const int N=100010,INF=0x3f3f3f3f;
    struct Node{
    	int u,sizeu,v,sizev;
    };
    typedef std::pair <int,int> pii;
    pii edge[N<<1];
    std::vector <int> tree[N<<2];
    int n,m,tlim;
    int f[N<<1],size[N<<1];
    std::stack <Node> sta;
    
    inline int read(void){
    	int res,f=1;
    	char c;
    	while((c=getchar())<'0'||c>'9')
    		if(c=='-')f=-1;
    	res=c-48;
    	while((c=getchar())>='0'&&c<='9')
    		res=res*10+c-48;
    	return res*f;
    }
    inline int lc(int x){
    	return x<<1;
    }
    inline int rc(int x){
    	return x<<1|1;
    }
    inline int geta(pii x){
    	return x.first;
    }
    inline int getb(pii x){
    	return x.second;
    }
    void change(int k,int l,int r,int L,int R,int x){
    	if(L<=l&&r<=R){
    		tree[k].push_back(x);
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(L<=mid)
    		change(lc(k),l,mid,L,R,x);
    	if(mid<R)
    		change(rc(k),mid+1,r,L,R,x);
    	return;
    }
    inline int find(int x){
    	return (f[x]==x)?x:find(f[x]); 
    }
    inline void Union(int u,int v){
    	u=find(u),v=find(v);
    	if(size[u]>size[v])
    		std::swap(u,v);
    	sta.push((Node){u,size[u],v,size[v]});
    	f[u]=v,size[v]+=size[u];
    	return;
    }
    void calc(int k,int l,int r){
    	bool flag=true;
    	int now=sta.size();
    	for(int i=0;i<(int)tree[k].size();++i){
    		int u=geta(edge[tree[k][i]]),v=getb(edge[tree[k][i]]);
    		if(find(u)==find(v)){
    			for(int p=l;p<=r;++p)
    				puts("No");
    			flag=false;
    			break;
    		}
    		Union(u,v+n),Union(v,u+n);
    	}
    	int mid=(l+r)>>1;
    	if(flag){
    		if(l==r)
    			puts("Yes");
    		else
    			calc(lc(k),l,mid),calc(rc(k),mid+1,r);
    	}
    	while((int)sta.size()>now){
    		Node val=sta.top();
    		sta.pop();
    		f[val.u]=val.u,f[val.v]=val.v,size[val.u]=val.sizeu,size[val.v]=val.sizev;
    	}
    	return;
    }
    int main(void){
    	n=read(),m=read(),tlim=read();
    	for(int i=1;i<=2*n;++i){
    		f[i]=i,size[i]=1;
    	}
    	for(int i=1,l,r;i<=m;++i){
    		edge[i].first=read(),edge[i].second=read();
    		l=read(),r=read();
    		if(l!=r)
    			change(1,1,tlim,l+1,r,i);
    	}
    	calc(1,1,tlim);
    	return 0;
    }
    
    

    例题 2 Luogu P4141 消失之物

    题意

    有一个 \(n\) 个物品和一个背包。问对于每一个物品,其消失后装满容量为 \(1 \sim m\) 的背包的方案数。

    \(1 \leq n,m \leq 2000\)

    题解

    可以看成是物品 \(i\)\([1,i-1]\)\([i+1,n]\) 出现。

    当然,有一种更简便的写法:查询左区间就把右区间的所有物品加入背包,查询结束之后撤销。查询右区间同理。

    # include <bits/stdc++.h>
    
    const int N=2010,INF=0x3f3f3f3f;
    
    int f[20][N];
    int a[N];
    int n,m;
    
    inline int read(void){
    	int res,f=1;
    	char c;
    	while((c=getchar())<'0'||c>'9')
    		if(c=='-')f=-1;
    	res=c-48;
    	while((c=getchar())>='0'&&c<='9')
    		res=res*10+c-48;
    	return res*f;
    }
    void solve(int l,int r,int dep){
    	if(l==r){
    		for(int i=1;i<=m;++i){
    			printf("%d",f[dep][i]);
    		}
    		puts("");
    		return;
    	}
    	int mid=(l+r)>>1;
    	for(int i=0;i<=m;++i)
    		f[dep+1][i]=f[dep][i];
    	for(int i=mid+1;i<=r;++i){
    		for(int j=m;j>=a[i];--j){
    			f[dep+1][j]=(f[dep+1][j]+f[dep+1][j-a[i]])%10;
    		}
    	}
    	solve(l,mid,dep+1);
    	for(int i=0;i<=m;++i)
    		f[dep+1][i]=f[dep][i];
    	for(int i=l;i<=mid;++i){
    		for(int j=m;j>=a[i];--j){
    			f[dep+1][j]=(f[dep+1][j]+f[dep+1][j-a[i]])%10;
    		}
    	}
    	solve(mid+1,r,dep+1);
    	return;
    }
    int main(void){
    	n=read(),m=read();
    	for(int i=1;i<=n;++i){
    		a[i]=read();
    	}
    	f[1][0]=1;
    	solve(1,n,1);
    	return 0;
    }
    
  • 相关阅读:
    【动态规划】Column Addition @ICPC2017Tehran/upcexam5434
    【高精度&想法题】Count the Even Integers @ICPC2017HongKong/upcexam5563#Java
    【二分】Base Station Sites @ICPC2017HongKong/upcexam5559
    【想法题】Knot Puzzle @AtCoder Grand Contest 002 C/upcexam5583
    【搜索】传感器 @upcexam6023
    Unity 3D 开发 —— 脚本编程
    Kinect 开发 —— 骨骼数据与彩色影像和深度影像的对齐
    Kinect 开发 —— 杂一
    Kinect 开发 —— 面部追踪
    Kinect 开发 —— Kinect studio
  • 原文地址:https://www.cnblogs.com/liuzongxin/p/15256550.html
Copyright © 2011-2022 走看看