[bzoj5331][莫队算法]原题识别

Description

传送门

题解

肝了一上午终于肝出来了实在是感天动地啊…
感觉这种题考场上还是只能写个60分跑路了…
看一看这个第一问,显然可以无脑树上莫队搞过去
于是你就有 s u b t a s k 1 subtask1 subtask1 30 p t s 30pts 30pts
那我们考虑这个第二问怎么做
先把问题放到序列上
那莫队的时候,我们只需要维护最左边这个点的答案和最右边这个点的答案
什么答案?
就是把左端点固定为最左边这个点,右端点在当前区间内任取,做操作1的答案和
右端点的答案也同理了
维护的话,以左端点为例
我们扩大左端点的时候,看一下新点的颜色在之前有没有出现过
显然不管有没有出现过,这个新点的贡献一定都是当前序列的长度和
因为所有没有取到这个新点的左边的右端点都能计算到这个新点的贡献
那如果之前出现过的话,把之前的贡献减掉就好了
扩大右端点的时候,左端点的答案会增大当前的颜色个数
所以这样维护就可以了
那只需要再维护一个当前序列,每个点的答案和就可以了
就每次扩大的时候加上新的左/右端点的贡献,减小的时候减去去掉的左/右端点的贡献
于是就可以 n n n\sqrt n nn 获得 s u b t a s k 2 subtask2 subtask2 30 p t s 30pts 30pts
六十分愉快到手
然后考虑怎么扩展到树上
不会啊考场跑路了
观察数据生成的方式,除了 [ 1 , p ] [1,p] [1,p]是固定生成的一条链之外,这棵树的形态是随机的
我们把固定的那条链称为主链
于是任意一个点到主链的长度期望是 l o g n logn logn
我们把询问点对 ( u , v ) (u,v) (u,v)到主链分别遇见的第一个点称作 ( x , y ) (x,y) (x,y),满足 d e p [ x ] &lt; = d e p [ y ] dep[x]&lt;=dep[y] dep[x]<=dep[y]
先考虑 x ≠ y x \neq y x̸=y的情况,那么这时候 L C A ( u , v ) = x LCA(u,v)=x LCA(u,v)=x
一个询问就可以变成主链上 ( x , y ) (x,y) (x,y)的询问
那么对主链莫队
于是可以拆成几部分分别计算
1:
首先计算 ( 1 − &gt; x ) (1-&gt;x) (1>x)以及 ( 1 − &gt; y ) (1-&gt;y) (1>y)每个点的答案
这里的答案指的是当一个点选择当前点另一个点选择不在当前点之下的操作一答案和
但是我们注意到,这部分计算的答案有一些是不合法的
即两个端点分别选择了 ( u − &gt; x ) (u-&gt;x) (u>x)路径上的点或者 ( v − &gt; x ) (v-&gt;x) (v>x)路径上的点的情况
注意两个点都不能选择 x x x这个点
那么这里用上面提到的莫队推广一下就可以暴力算了
2:
再考虑另一部分答案即一个点选择 ( u − &gt; x ) (u-&gt;x) (u>x)路径上的点
另一个点选择 ( v − &gt; x ) (v-&gt;x) (v>x)路径上的点的答案
注意这里两个点也都不能选择 x x x这个点
这里也用上面的莫队推广一下就可以暴力算了
可以把选择 x x x的情况容斥一下就没有了
或者看一下代码就可以理解了…
那还有 x = y x=y x=y的情况,这时候怎么办…凉凉了嘛
那他们两个之间的路径长度期望就是 l o g n logn logn的了
于是他们算答案第二部分直接暴力做就可以了
第一部分都是一样最后扫就可以了
为了方便第一问可以在做第二问的时候顺便做掉
就不需要树上莫队了qwq
第一次码上10K…
说实话码完真的爽
复杂度是还能跑的动的 n n + n l o g 2 n n\sqrt n+n log^2n nn +nlog2n

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#include<bitset>
#include<set>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define pll pair<long long,long long>
#define pii pair<int,int>
using namespace std;
inline int read()
{
	int f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int stack[20];
inline void write(LL x)
{
	if(x<0){putchar('-');x=-x;}
    if(!x){putchar('0');return;}
    int top=0;
    while(x)stack[++top]=x%10,x/=10;
    while(top)putchar(stack[top--]+'0');
}
inline void pr1(int x){write(x);putchar(' ');}
inline void pr2(LL x){write(x);putchar('\n');}
const int MAXN=100005;
const int MAXM=200005;
struct edge{int x,y,next;}E[2*MAXN];int len,last[MAXN];
void ins(int x,int y){len++;E[len].x=x;E[len].y=y;E[len].next=last[x];last[x]=len;}

unsigned int SA,SB,SC;
unsigned int rng61()
{
	SA^=SA<<16;
	SA^=SA>>5;
	SA^=SA<<1;
	unsigned int t=SA;
	SA=SB;
	SB=SC;
	SC^=t^SA;
	return SC;
}
int n,p;
int a[MAXN];
int pre[MAXN],nxt[MAXN];
int oo[MAXN];
void gen()
{
	n=read();p=read();SA=read();SB=read();SC=read();
	for(int i=2;i<=p;i++)ins(i-1,i),ins(i,i-1);
	for(int i=p+1;i<=n;i++)
	{
		int u=rng61()%(i-1)+1;
		ins(u,i);ins(i,u);
	}
	for(int i=1;i<=n;i++)a[i]=rng61()%n+1;
	memset(oo,-1,sizeof(oo));
	for(int i=1;i<=p;i++)pre[i]=oo[a[i]],oo[a[i]]=i;
	memset(oo,63,sizeof(oo));
	for(int i=p;i>=1;i--)nxt[i]=oo[a[i]],oo[a[i]]=i;
}


LL answer[MAXM];
struct ask{int x,y,u,v,o,q;}w[MAXM];
int pos[MAXN],block;
bool cmp(ask n1,ask n2){return pos[n1.x]!=pos[n2.x]?pos[n1.x]<pos[n2.x]:n1.y<n2.y;}

int fa[25][MAXN],dep[MAXN],bin[25];
void pre_tree_node(int x)
{
	for(int i=1;bin[i]<=dep[x];i++)fa[i][x]=fa[i-1][fa[i-1][x]];
	for(int k=last[x];k;k=E[k].next)
	{
		int y=E[k].y;
		if(y!=fa[0][x])
		{
			fa[0][y]=x;dep[y]=dep[x]+1;
			pre_tree_node(y);
		}
	}
}
int findfirst(int x)//找他在链上的第一个点 
{
	if(x<=p)return x;
	for(int i=20;i>=0;i--)if(bin[i]<=dep[x]&&fa[i][x]>p)x=fa[i][x];
	return fa[0][x];
}
int m;
int fir[MAXN],lst[MAXN];
LL sum1,sum2,zg,tot;
int L,R;
void add_l(int x)
{
	sum2+=tot;
	if(fir[a[x]]==0)//没有点 
	{
		fir[a[x]]=lst[a[x]]=x;
		tot++;sum1+=R-x+1;
		sum2+=x-L+1;
	}
	else
	{
		sum1-=R-fir[a[x]]+1;
		sum1+=R-x+1;
		fir[a[x]]=x;
	}
	zg+=sum1;
}
void add_r(int x)
{
	sum1+=tot;
	if(lst[a[x]]==0)//没有点
	{
		fir[a[x]]=lst[a[x]]=x;
		tot++;sum2+=x-L+1;
		sum1+=R-x+1;
	}
	else
	{
		sum2-=lst[a[x]]-L+1;
		sum2+=x-L+1;
		lst[a[x]]=x;
	}
	zg+=sum2;
}
void del_l(int x)
{
	zg-=sum1;
	if(nxt[x]>R)//下一个没有点了
	{
		lst[a[x]]=fir[a[x]]=0;
		tot--;sum2-=x-L+1;sum1-=R-x+1;
	}
	else
	{
		sum1-=R-x+1;
		sum1+=R-nxt[x]+1;
		fir[a[x]]=nxt[x];
	}
	sum2-=tot;
}
void del_r(int x)
{
	zg-=sum2;	
	if(pre[x]<L)//前一个没有点了
	{
		lst[a[x]]=fir[a[x]]=0;
		tot--;sum2-=x-L+1;sum1-=R-x+1;
	}
	else
	{
		sum2-=x-L+1;
		sum2+=pre[x]-L+1;
		lst[a[x]]=pre[x];
	}
	sum1-=tot;
}
int vis[MAXN],vis1[MAXN],tim,tim1;
int nt[MAXN],gg;
int nt1[MAXN],gg1;
int c1[MAXN],t1;
int c2[MAXN],t2;
void solve(int d1,int f1,int d2,int f2,int q,int o)
{
	if(q==1)
	{
		int ans=tot;
		tim++;
		while(d1!=f1)
		{
			if(vis[a[d1]]!=tim&&!fir[a[d1]])vis[a[d1]]=tim,ans++;
			d1=fa[0][d1];
		}
		while(d2!=f2)
		{
			if(vis[a[d2]]!=tim&&!fir[a[d2]])vis[a[d2]]=tim,ans++;
			d2=fa[0][d2];
		}
		answer[o]=ans;
	}
	else
	{
		del_l(L);L++;
		int x=d2;tim++;
		LL sum=zg,su=sum2;gg=0;
		while(x!=f2)nt[++gg]=x,x=fa[0][x];
		int gk=gg;
		if(gg)
		{
			do
			{
				x=nt[gg--];
				if(vis[a[x]]!=tim)
				{
					vis[a[x]]=tim,su+=dep[x]-dep[L]+1;vis1[a[x]]=x;
					if(lst[a[x]])su-=dep[lst[a[x]]]-dep[L]+1;
				}
				else
				{
					su-=dep[vis1[a[x]]]-dep[L]+1;su+=dep[x]-dep[L]+1;
					vis1[a[x]]=x;
				}
				sum+=su;
			}while(gg);
		}
		gg=gk;
		answer[o]-=sum;sum=0;
		x=d1;tim++;gg1=0;
		
		t1=0;
		while(x!=f1)
		{
			c1[++t1]=a[x];
			nt1[++gg1]=x;
			x=fa[0][x];
		}
		for(int i=1;i<=t1;i++)
		{
			tim++;LL cnt=0;
			for(int j=i;j<=t1;j++)
			{
				if(vis[c1[j]]!=tim)vis[c1[j]]=tim,cnt++;
				sum+=cnt;
			}
		}
		nt1[++gg1]=x;
		answer[o]-=sum;sum=0;
		
		LL lin=0,temp=gg1;
		x=0;tim++;
		if(gg1)
		{
			do
			{
				x=nt1[gg1--];
				if(vis[a[x]]!=tim)vis[a[x]]=tim,lin++;
				if(x!=f1)sum+=lin;
			}while(x!=d1&&gg1>=1);
		}
		answer[o]-=sum;sum=0;
		
		add_l(--L);
		
		tim++;
		x=0;
		sum=sum1+1LL*tot*(dep[d2]-dep[f2]);
		if(gg)
		{
			do
			{
				x=nt[gg--];
				if(vis[a[x]]!=tim&&!fir[a[x]])sum+=dep[d2]-dep[x]+1,vis[a[x]]=tim,vis1[a[x]]=x;	
			}while(x!=d2&&gg>=1);
		}
		x=0;gg1=temp-1;
		
		LL val=dep[d2]-dep[f1]+1;
		tim1++;//关注这个点有没有在左边这条链出现 
		if(gg1>0)
		{
			do
			{
				x=nt1[gg1--];
				if(oo[a[x]]!=tim1)//没有出现过
				{
					oo[a[x]]=tim1;
					if(fir[a[x]])//在主链上
					{
						sum-=dep[d2]-dep[fir[a[x]]]+1;
						sum+=val;
					}
					else
					{
						if(vis[a[x]]==tim)
						{
							sum-=dep[d2]-dep[vis1[a[x]]]+1;
							sum+=val;
						}
						else sum+=val;
					}
				}
				answer[o]+=sum;
			}while(x!=d1&&gg1>=1);
		}
	}
}

vector<pii> vec[MAXN];

LL temp,needad;
void qry(int x,int fa)
{
	for(int i=0;i<vec[x].size();i++)
	{
		int u=vec[x][i].first;
		answer[u]+=needad*vec[x][i].second;
	}
	for(int k=last[x];k;k=E[k].next)
	{
		int y=E[k].y;
		if(y!=fa)
		{
			int lin=vis[a[y]];
			if(vis[a[y]]==0)
			{
				temp+=dep[y]+1;
				vis[a[y]]=y;needad+=temp;
				qry(y,x);
				vis[a[y]]=0;needad-=temp;
				temp-=dep[y]+1;
			}
			else
			{
				temp-=dep[vis[a[y]]]+1;temp+=dep[y]+1;
				vis[a[y]]=y;needad+=temp;
				qry(y,x);
				vis[a[y]]=lin;needad-=temp;
				temp-=dep[y]+1;temp+=dep[vis[a[y]]]+1;
			}
		}
	}
}
void clear()
{
	memset(vis,0,sizeof(vis));
	memset(lst,0,sizeof(lst));memset(fir,0,sizeof(fir));
}
void vio1(int u,int v,int o)
{
	int x=u,y=v;t1=t2=0;
	while(x!=y)
	{
		if(dep[x]>dep[y])c1[++t1]=a[x],x=fa[0][x];
		else if(dep[x]<dep[y])c2[++t2]=a[y],y=fa[0][y];
		else c1[++t1]=a[x],c2[++t2]=a[y],x=fa[0][x],y=fa[0][y];
	}
	LL ans=0;
	ans-=dep[x]+1;
	for(int i=t1;i>=1;i--)
	{
		tim++;LL cnt=0;
		for(int j=i;j<=t1;j++)if(vis[c1[j]]!=tim)vis[c1[j]]=tim,cnt++;
		if(vis[a[x]]!=tim)vis[a[x]]=tim,cnt++;
		for(int j=t2;j>=1;j--)
		{
			if(vis[c2[j]]!=tim)vis[c2[j]]=tim,cnt++;
			ans+=cnt;
		}
	}
	for(int i=t1;i>=1;i--)
	{
		tim++;LL cnt=0;
		for(int j=i;j<=t1;j++)
		{
			if(vis[c1[j]]!=tim)vis[c1[j]]=tim,cnt++;
			ans-=cnt;
		}
	}
	for(int i=t2;i>=1;i--)
	{
		tim++;LL cnt=0;
		for(int j=i;j<=t2;j++)
		{
			if(vis[c2[j]]!=tim)vis[c2[j]]=tim,cnt++;
			ans-=cnt;
		}
	}
	answer[o]=ans;
}

void vio(int u,int v,int o)
{
	int x=u,y=v;t1=t2=0;
	while(x!=y)
	{
		if(dep[x]>dep[y])c1[++t1]=a[x],x=fa[0][x];
		else if(dep[x]<dep[y])c2[++t2]=a[y],y=fa[0][y];
		else c1[++t1]=a[x],c2[++t2]=a[y],x=fa[0][x],y=fa[0][y];
	}
	c1[++t1]=a[x];
	for(int i=t2;i>=1;i--)c1[++t1]=c2[i];
	LL ans=0;
	tim++;
	for(int i=1;i<=t1;i++)if(vis[c1[i]]!=tim)vis[c1[i]]=tim,ans++;
	answer[o]=ans;
}
void print()
{
	for(int i=1;i<len;i+=2)printf("%d %d\n",E[i].x,E[i].y);
	for(int i=1;i<=n;i++)printf("%d ",a[i]);
	puts("");
}
int main()
{
	bin[0]=1;for(int i=1;i<=20;i++)bin[i]=bin[i-1]<<1;	
	int T=read();while(T--)
	{
		clear();
		len=0;memset(last,0,sizeof(last));
		for(int i=1;i<=n;i++)vec[i].clear();
		memset(answer,0,sizeof(answer));
		gen();
		block=sqrt(p);
		for(int i=1;i<=p;i++)pos[i]=(i-1)/block+1;
		pre_tree_node(1);
		m=read();
		memset(vis,0,sizeof(vis));tim=0;
		for(int i=1;i<=m;i++)
		{
			w[i].q=read();w[i].u=read();w[i].v=read();w[i].o=i;
			w[i].x=findfirst(w[i].u);w[i].y=findfirst(w[i].v);
			if(w[i].x>w[i].y)swap(w[i].x,w[i].y),swap(w[i].u,w[i].v);
			
			if(w[i].q!=1)vec[w[i].u].push_back(mp(i,1)),vec[w[i].v].push_back(mp(i,1));
			if(w[i].x==w[i].y&&w[i].q==1)vio(w[i].u,w[i].v,i);
			else if(w[i].x==w[i].y&&w[i].q==2)vio1(w[i].u,w[i].v,i);
		}
//		print();
		tim=tim1=0;
		memset(vis,0,sizeof(vis));
		memset(oo,0,sizeof(oo));
		sort(w+1,w+1+m,cmp);
		L=R=sum1=sum2=zg=tot=0;
		for(int i=1;i<=m;i++)
		{
			while(R<w[i].y)add_r(++R);
			while(R>w[i].y)del_r(R),R--;
			while(L<w[i].x)del_l(L),L++;
			while(L>w[i].x)add_l(--L);
			
			if(w[i].x!=w[i].y)solve(w[i].u,w[i].x,w[i].v,w[i].y,w[i].q,w[i].o);
			if(w[i].q!=1&&w[i].x!=w[i].y)answer[w[i].o]-=dep[w[i].x]+1;
		}
		
		memset(vis,0,sizeof(vis));
		vis[a[1]]=1;temp=needad=1;
		qry(1,0);
		for(int i=1;i<=m;i++)pr2(answer[i]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值