CF1801E Gasoline prices 解题报告

当数据范围较小时,显然可以暴力合并每个点,但是当n较大时,枚举的时间复杂度会很大

但是可以发现,真正有效的合并只有n-1次,主要的时间浪费在遍历链上

所以,如何能快速找到未合并的点?

可以采用一种类似线段树的方法,利用倍增,递归求解

而合并就分成了同向和逆向,两种情况

先说同向,假设当前的两个点为u,v,它们在向上2t长度的链没有被合并,那么在这段链中,一定还有点未被合并

此时,若t=0,则是这两个点未被合并,并查集合并更新l,r即可

若t>0,则递归对长度2t1的两条链求解

最后,将这两段合并即可

逆向同理,但是只有一个点暴力跳父亲

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int mod=1e9+7;
int n,edgenum,head[200005],l[200005],r[200005],q;
ll ans;
ll qpow(ll x,int y)
{
	ll res=1;
	while(y)
	{
		if(y&1) res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
struct edge{
	int to,nxt;
}e[400005];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
int f1[200005][23],f[200005],f2[400005][23];
int dep[200005];
void dfs(int u,int fa)
{
	dep[u]=dep[fa]+1;
	f[u]=u,f2[u][0]=u,f2[u+n][0]=n+u;
	f1[u][0]=fa;
	for(int i=1;i<=17;i++)
	{
		f1[u][i]=f1[f1[u][i-1]][i-1];
		f2[u][i]=u,f2[u+n][i]=n+u;
	}
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
	}
}
int lca(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=17;i>=0;i--)
	{
		if(dep[f1[x][i]]>=dep[y])
		{
			x=f1[x][i];
		}
		if(x==y) return x;
	}
	for(int i=17;i>=0;i--)
	{
		if(f1[x][i]!=f1[y][i])
		{
			x=f1[x][i];
			y=f1[y][i];
		}
	}
	return f1[x][0];
}
int find(int x,int y)
{
	if(f2[x][y]!=x) f2[x][y]=find(f2[x][y],y);
	return f2[x][y];
}
int find1(int x)
{
	if(f[x]!=x) f[x]=find1(f[x]);
	return f[x];
}
void merge(int u,int v)
{
//	printf("%d %d\n",u,v);
	int x=find1(u),y=find1(v);
	if(x==y) return;
	f[x]=y;
	ans=ans*qpow(max(r[x]-l[x]+1,0),mod-2)%mod;
	ans=ans*qpow(max(r[y]-l[y]+1,0),mod-2)%mod;
	l[y]=max(l[y],l[x]);
	r[y]=min(r[y],r[x]);
	ans=ans*max(0,r[y]-l[y]+1)%mod;
}
void merge1(int u,int v,int t)
{
	int x=find(u,t),y=find(v,t);
	if(x==y) return;
	if(t==0)
	{
		merge(u,v);
		return;
	}
	f2[x][t]=y;
	merge1(u,v,t-1);
	merge1(f1[u][t-1],f1[v][t-1],t-1);
}
int getfa(int u,int x)
{
	for(int i=0;i<=17;i++)
	{
		if(x&(1<<i)) u=f1[u][i];
	}
	return u;
}
void merge2(int u,int v,int t)
{
	int x=find(u,t),y=find(v+n,t);
//	printf("%d %d %d %d %d\n",u,v,x,y,t);
	if(x==y) return;
	if(t==0)
	{
		merge(u,v);
		return;
	}
	f2[x][t]=y;
	f2[find(u+n,t)][t]=find(v,t);
	merge2(u,f1[v][t-1],t-1);
	merge2(f1[u][t-1],v,t-1);
}
int main()
{
	scanf("%d",&n);
	for(int i=2;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		add_edge(i,x);
		add_edge(x,i);
	}
	dfs(1,0);
	ans=1;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&l[i],&r[i]);
		ans=ans*(r[i]-l[i]+1)%mod;
	}
//	printf("%lld\n",ans);
	scanf("%d",&q);
	while(q--)
	{
		int a,b,c,d;
		scanf("%d%d%d%d",&a,&b,&c,&d);
		int lca1=lca(a,b),lca2=lca(c,d);
		int len=min(dep[a]-dep[lca1],dep[c]-dep[lca2]);
		for(int i=17;i>=0;i--)
		{
			if((1<<i)&len)
			{
				merge1(a,c,i);
				a=f1[a][i],c=f1[c][i];
			}
		}
		len=min(dep[b]-dep[lca1],dep[d]-dep[lca2]);
		for(int i=17;i>=0;i--)
		{
			if((1<<i)&len)
			{
				merge1(b,d,i);
				b=f1[b][i],d=f1[d][i];
			}
		}
		if(a==lca1)
		{
			len=dep[b]-dep[lca1]+1;
			for(int i=17;i>=0;i--)
			{
				if((1<<i)&len)
				{
					len^=(1<<i);
					merge2(b,getfa(c,len),i);
					b=f1[b][i];
				}
			}
		}
		else
		{
			len=dep[a]-dep[lca1]+1;
			for(int i=17;i>=0;i--)
			{
				if((1<<i)&len)
				{
					len^=(1<<i);
					merge2(a,getfa(d,len),i);
					a=f1[a][i];
				}
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}
posted @   wangsiqi2010916  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示