CF1007D - Ants——2-sat、树链剖分优化建图

CF1007D Ants

题目描述

中文题面

题解

每个蚂蚁有两种选择,其中选择一条路径会导致所有与它有公共边的路径不能选,这就是经典的 2-sat 模型,建图后跑 t a r j a n \rm tarjan tarjan 即可。我们只需要把连边搞定。

所以现在需要解决的问题是,如何用较少的边从路径 ( u , v ) (u,v) (u,v) 向所有与它有公共边的路径(但不包括 ( u , v ) (u,v) (u,v) 本身)连边。

我们考虑两条有公共边的路径 ( a , b ) (a,b) (a,b) ( c , d ) (c,d) (c,d),假设 ( a , b ) (a,b) (a,b) L C A \rm LCA LCA 的深度 ≤ ( c , d ) \le(c,d) (c,d) L C A \rm LCA LCA 的深度:
在这里插入图片描述
你会发现 l c a ( c , d ) lca(c,d) lca(c,d) 下面的两条边(蓝色)中必有一条是在路径 ( a , b ) (a,b) (a,b) 上的,此时我们可以考虑用重链剖分优化建图,在线段树上进行 单点 → ( c , d ) \rightarrow (c,d) (c,d) 的连边,然后进行 ( a , b ) → (a,b)\rightarrow (a,b) 区间 的连边,这样就可以建出 ( a , b ) → ( c , d ) (a,b)\rightarrow (c,d) (a,b)(c,d) 的有向路径。

反过来同理,我们若要建出 ( c , d ) → ( a , b ) (c,d)\rightarrow (a,b) (c,d)(a,b) 的有向路径,可以先进行 区间 → ( a , b ) \rightarrow (a,b) (a,b) 的连边,相当于打上了永久化懒标记,然后进行 单点 → ( c , d ) \rightarrow (c,d) (c,d) 的连边,访问覆盖的所有懒标记即可。

但是直接一次性连边显然是不行的,因为会出现自己连向自己的问题。我们可以对所有路径按照 L C A \rm LCA LCA 的深度进行排序,然后用前缀+后缀的连边技巧。已知单点修改和永久化懒标记都是可以可持久化的,所以我们可以在每次连边时新建一个版本,这样就能对每个前缀和后缀的所有路径维护出可进行访问连边的线段树。每个路径只需对一个前缀和一个后缀的线段树进行连边即可。

直接重剖优化建图的点数边数和时间复杂度是 O ( m log ⁡ 2 n ) O(m\log^2n) O(mlog2n),用全局平衡二叉树即可全部降到 O ( m log ⁡ n ) O(m\log n) O(mlogn)

代码

超长的,但是大部分是复读机。

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define uns unsigned
#define IF (it->first)
#define IS (it->second)
#define END putchar('\n')
using namespace std;
const int MAXN=2000005;//因为太怕RE所以把数组开到了很大
const ll INF=1e17;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
inline void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}
inline ll lowbit(ll x){return x&-x;}

struct edge{
	int v,to;edge(){}
	edge(int V,int T){v=V,to=T;}
}e[10000006];
int EN=1,G[MAXN],D[MAXN],ps;
inline void addedge(int u,int v)//树边
	e[++EN]=edge(v,G[u]),G[u]=EN;
	e[++EN]=edge(u,G[v]),G[v]=EN;
}
inline void addedge_(int u,int v){//有向图边
	e[++EN]=edge(v,D[u]),D[u]=EN;
}
int n,m,a[MAXN>>5],b[MAXN>>5],lc[MAXN>>4],c[MAXN>>5],d[MAXN>>5];
int sr[MAXN>>4];
int dfn[MAXN],low[MAXN],IN,bl[MAXN],tot;
stack<int>st;
bool ist[MAXN];
inline void tarjan(int x){
	dfn[x]=low[x]=++IN,st.push(x),ist[x]=1;
	for(int i=D[x];i;i=e[i].to){
		int v=e[i].v;
		if(!dfn[v])tarjan(v),low[x]=min(low[x],low[v]);
		else if(ist[v])low[x]=min(low[x],dfn[v]);
	}
	if(low[x]==dfn[x]){
		tot++;
		while(!st.empty()&&dfn[st.top()]>=dfn[x])
			bl[st.top()]=tot,ist[st.top()]=0,st.pop();
	}
}
int pi[MAXN],p[MAXN];
int hd[MAXN],id[MAXN],NN,tp[MAXN],tl[MAXN],ls[MAXN],rs[MAXN];
int fa[MAXN],dep[MAXN],siz[MAXN],hs[MAXN],rt[MAXN];
inline int build(int l,int r){//建立全局平衡二叉树
	if(l>r)return 0;
	int s=0,pr=0,x;
	for(int i=l;i<=r;i++)s+=siz[id[i]]-siz[hs[id[i]]];
	for(x=l;x<r;x++){
		pr+=siz[id[x]]-siz[hs[id[x]]];
		if((pr<<1)>s)break;
	}
	p[x]=++ps,pi[x]=++ps;
	addedge_(pi[x],p[x]);
	ls[x]=build(l,x-1);
	if(ls[x])addedge_(pi[x],pi[ls[x]]);
	rs[x]=build(x+1,r);
	if(rs[x])addedge_(pi[x],pi[rs[x]]);
	return x;
}
//一堆连边函数
inline void adl1(int x,int z,int to){
	if(!x)return;
	if(x==z){
		addedge_(++ps,to),addedge_(ps,p[x]),p[x]=ps;
		addedge_(++ps,to),addedge_(ps,pi[x]),pi[x]=ps;
		return;
	}addedge_(++ps,to),addedge_(ps,pi[x]),pi[x]=ps;
	if(z<x)adl1(ls[x],z,to);
	else adl1(rs[x],z,to);
}
inline void adl2(int x,int l,int r,int a,int b,int z){
	if(l>r)return;
	if(l>=a&&r<=b){addedge_(z,pi[x]);return;}
	if(x>=a&&x<=b)addedge_(z,p[x]);
	if(a<x)adl2(ls[x],l,x-1,a,b,z);
	if(b>x)adl2(rs[x],x+1,r,a,b,z);
}
inline void adl1_(int x,int z,int to){
	if(!x)return;
	if(x==z){addedge_(to,p[x]),addedge_(to,pi[x]);return;}
	addedge_(to,pi[x]);
	if(z<x)adl1_(ls[x],z,to);
	else adl1_(rs[x],z,to);
}
inline void adl2_(int x,int l,int r,int a,int b,int z){
	if(l>r)return;
	if(l>=a&&r<=b){
		addedge_(++ps,z),addedge_(ps,pi[x]),pi[x]=ps;
		return;
	}
	if(x>=a&&x<=b)addedge_(++ps,z),addedge_(ps,p[x]),p[x]=ps;
	if(a<x)adl2_(ls[x],l,x-1,a,b,z);
	if(b>x)adl2_(rs[x],x+1,r,a,b,z);
}
//建立重剖
inline void dfs1(int x){
	dep[x]=dep[fa[x]]+1,siz[x]=1,hs[x]=0;
	for(int i=G[x];i;i=e[i].to){
		int v=e[i].v;
		if(v==fa[x])continue;
		fa[v]=x,dfs1(v),siz[x]+=siz[v];
		if(siz[v]>siz[hs[x]])hs[x]=v;
	}
}
inline void dfs2(int x){
	hd[x]=++NN,id[NN]=x;
	tp[x]=(x==hs[fa[x]]?tp[fa[x]]:x),tl[tp[x]]=NN;
	if(hs[x])dfs2(hs[x]);
	for(int i=G[x];i;i=e[i].to){
		int v=e[i].v;
		if(v==fa[x]||v==hs[x])continue;
		dfs2(v);
	}if(x==tp[x])rt[x]=build(hd[x],tl[x]);
}
inline int lca(int u,int v){
	while(tp[u]^tp[v]){
		if(dep[tp[u]]<dep[tp[v]])swap(u,v);
		u=fa[tp[u]];
	}return dep[u]<dep[v]?u:v;
}
//最外层连边函数
inline void adr1(int u,int v,int z,bool op){
	int p=0,q=0;
	while(tp[u]^tp[v]){
		if(dep[tp[u]]<dep[tp[v]])swap(u,v),swap(p,q);
		p=tp[u],u=fa[tp[u]];
	}
	if(dep[u]>dep[v])swap(u,v),swap(p,q);
	if(u^v)v=u,q=hs[u];
	if(p)op?adl1(rt[tp[p]],hd[p],z):adl1_(rt[tp[p]],hd[p],z);
	if(q)op?adl1(rt[tp[q]],hd[q],z):adl1_(rt[tp[q]],hd[q],z);
}
inline void adr2(int u,int v,int z,bool op){
	while(tp[u]^tp[v]){
		if(dep[tp[u]]<dep[tp[v]])swap(u,v);
		if(op)adl2(rt[tp[u]],hd[tp[u]],tl[tp[u]],hd[tp[u]],hd[u],z);
		else adl2_(rt[tp[u]],hd[tp[u]],tl[tp[u]],hd[tp[u]],hd[u],z);
		u=fa[tp[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	if(u==v)return;
	if(op)adl2(rt[tp[u]],hd[tp[u]],tl[tp[u]],hd[u]+1,hd[v],z);
	else adl2_(rt[tp[u]],hd[tp[u]],tl[tp[u]],hd[u]+1,hd[v],z);
}
signed main()
{
	n=read();
	for(int i=1;i<n;i++)addedge(read(),read());
	m=read(),ps=(m<<1);
	for(int i=1;i<=m;i++)
		a[i]=read(),b[i]=read(),c[i]=read(),d[i]=read();
	dfs1(1),dfs2(1);
	for(int i=1;i<=m;i++)
		lc[i]=lca(a[i],b[i]),sr[i]=i,lc[i+m]=lca(c[i],d[i]),sr[i+m]=i+m;
	sort(sr+1,sr+1+(m<<1),[](int x,int y){
		return dep[lc[x]]>dep[lc[y]];
	});
	for(int i=1;i<=(m<<1);i++){
		int x=sr[i];
		if(x<=m)adr2(a[x],b[x],x,1),adr1(a[x],b[x],x+m,1);
		else x-=m,adr2(c[x],d[x],x+m,1),adr1(c[x],d[x],x,1);
	}
	for(int i=1;i<=n;i++)p[i]=++ps,pi[i]=++ps,addedge_(pi[i],p[i]);
	for(int i=1;i<=n;i++){
		if(ls[i])addedge_(pi[i],pi[ls[i]]);
		if(rs[i])addedge_(pi[i],pi[rs[i]]);
	}
	for(int i=(m<<1);i>0;i--){
		int x=sr[i];
		if(x<=m)adr1(a[x],b[x],x,0),adr2(a[x],b[x],x+m,0);
		else x-=m,adr1(c[x],d[x],x+m,0),adr2(c[x],d[x],x,0);
	}
	for(int i=1;i<=ps;i++)if(!dfn[i])tarjan(i);
	for(int i=1;i<=m;i++)
		if(bl[i]==bl[i+m])return printf("NO\n"),0;
	printf("YES\n");
	for(int i=1;i<=m;i++)print(1+(bl[i]>bl[i+m]));
	return 0;
}

后记

有一种更简单的连边方式,只用进行区间的连边,但是要用一下懒标记下放的技术。这个下放相当于复制一个指针,不需要新建虚点和连边,只是全局平衡二叉树处理的时候会稍微麻烦一点。

可惜我过于愚蠢,领悟不到此法而搞出一个屎山。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值