[省选联考 2021 A/B 卷] 宝石

前言

考场上遭到降智打击,没切这个**题。

题目

洛谷

讲解

不需要主席树,不需要整体二分,只需要简简单单的倍增!

首先我们可以将一个询问拆成两段:从起点 ( t s)( t LCA),从 ( t LCA) 到终点 ( t t)

第一部分

对于从起点 ( t s)( t LCA),我们显然只需要贪心选即可。

对于每个点,我们可以用倍增维护从当前点向上的颜色构成的递增的等差数列,也就是说如果当前点的颜色为 ( t w_x),对应的宝石序列中的宝石为 ( t P_y),用 ( t dp_{x,i}) 表示从当前点向上走,选择宝石序列 ( t P_y)(P_{y+2^i}) 需要走到哪一个节点。

再记录每个点 ( t x) 到根的路径上第一个出现的 ( t P_1) 在哪个位置,记为 ( t fir_x),对于询问只需要将起点设为 ( t fir_s) 再倍增即可。

算上预处理,只需要 (O((n+Q)log_2n))

第二部分

同样的,我们可以维护递减的等差数列。但是我们倒着拿宝石从哪一个开始拿呢?

显然这个答案具有单调性,二分!

求出终点 ( t t) 向上最多选出的颜色序列后,与第一部分选出来的颜色序列进行比较,看看能否拼在一起即可。

但是当我们二分出来一个颜色后,怎么找这个颜色在哪里呢?

很简单,我们只需要一遍dfs,跑的时候记录一下从当前节点到根每个颜色的第一个出现位置即可。

当然,我们需要按照 dfs序 遍历这棵树,所以我们还需要对询问进行离线,并按照 ( t t_i) 的 dfs序 进行排序,以方便询问。

时间复杂度 (O((n+Q)log_2nlog_2c))

不要告诉我你不会求 ( t LCA)

代码

建议不要看这份又丑又长的代码。

//12252024832524
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std;

typedef long long LL;
const int MAXN = 200005;
const int MAXLOG = 18;
int n,m,c,LCA,Q;
int p[MAXN],w[MAXN],ans[MAXN],rp[MAXN];
bool hs[MAXN];

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x * 10) + (c ^ 48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) x = -x,putchar('-');
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x,T y){return x < 0 ? -x : x;}

int head[MAXN],tot;
struct edge
{
	int v,nxt;
}e[MAXN << 1];
void Add_Edge(int x,int y)
{
	e[++tot].v = y;
	e[tot].nxt = head[x];
	head[x] = tot;
}
void Add_Double_Edge(int x,int y)
{
	Add_Edge(x,y);
	Add_Edge(y,x);
}

int d[MAXN],f[MAXN],siz[MAXN],son[MAXN],clr[MAXN],dp[2][MAXN][MAXLOG],fir[MAXN],dfn[MAXN],dfntot;//0:s到lca; 1:t到lca 
void dfs1(int x,int fa) 
{
	dfn[x] = ++dfntot; 
	d[x] = d[fa] + 1; f[x] = fa; siz[x] = 1;
	int dz = clr[w[x]];
	clr[w[x]] = x;
	fir[x] = clr[p[1]]; 
	if(rp[w[x]]) dp[0][x][0] = clr[p[rp[w[x]]+1]],dp[1][x][0] = clr[p[rp[w[x]]-1]];
	for(int i = 0;i < 2;++ i)
		for(int j = 1;j < MAXLOG;++ j)
			dp[i][x][j] = dp[i][dp[i][x][j-1]][j-1];
	for(int i = head[x]; i ;i = e[i].nxt)
		if(e[i].v != fa)
		{
			dfs1(e[i].v,x);
			if(siz[e[i].v] > siz[son[x]]) son[x] = e[i].v;
			siz[x] += siz[e[i].v];
		}
	clr[w[x]] = dz; 
}
int tp[MAXN];
void dfs2(int x,int t)
{
	tp[x] = t;
	if(!son[x]) return;
	dfs2(son[x],t);
	for(int i = head[x]; i ;i = e[i].nxt)
		if(d[e[i].v] > d[x] && e[i].v != son[x])
			dfs2(e[i].v,e[i].v);
}
int lca(int x,int y)
{
	while(tp[x] != tp[y])
	{
		if(d[f[tp[x]]] < d[f[tp[y]]]) swap(x,y);
		x = f[tp[x]];
	}
	return d[x] > d[y] ? y : x;
}

int now = 1;
struct Query
{
	int s,t,ID;
	bool operator < (const Query &px)const{
		return dfn[t] < dfn[px.t];
	}
}q[MAXN];
void dfs3(int x)
{
	if(now > Q) return;
	int dz = clr[w[x]];
	clr[w[x]] = x;
	while(now <= Q && dfn[q[now].t] == dfn[x])
	{
		int s = q[now].s,t = q[now].t,ANS = 0;
		LCA = lca(s,t);
		s = fir[s];
		if(d[s] >= d[LCA])
			for(int i = MAXLOG-1;i >= 0;-- i)
				if(d[dp[0][s][i]] >= d[LCA])
					s = dp[0][s][i],ANS += (1 << i);
		int l = rp[w[s]]+1,r = c,ret = rp[w[s]],sclr = rp[w[s]];//l,r是第几个颜色,而不是直接表示颜色 
		if(d[s] < d[LCA]) l = 1,r = c,sclr = ret = 0;
		else ANS++;
		while(l <= r)
		{
			int mid = (l+r) >> 1;
			if(d[clr[p[mid]]] >= d[LCA]) 
			{
				t = clr[p[mid]];
				for(int i = MAXLOG-1;i >= 0;-- i)
					if(d[dp[1][t][i]] >= d[LCA])
						t = dp[1][t][i];
				if(sclr + 1 >= rp[w[t]]) ret = mid,l = mid+1;
				else r = mid-1;
			}
			else r = mid-1;
		}
		ans[q[now].ID] = ANS + Max(0,ret - ((d[s] < d[LCA]) ? 0 : rp[w[s]]));
		now++;
	}
	if(now > Q) return;
	for(int i = head[x]; i ;i = e[i].nxt)
		if(d[e[i].v] > d[x])
			dfs3(e[i].v);
	clr[w[x]] = dz; 
}

int main()
{
// 	freopen("gem.in","r",stdin);
// 	freopen("gem.out","w",stdout);
	n = Read(); m = Read(); c = Read();
	for(int i = 1;i <= c;++ i) p[i] = Read();
	for(int i = 1;i <= n;++ i) hs[w[i] = Read()] = 1;
	for(int i = 1;i < n;++ i) Add_Double_Edge(Read(),Read());
	for(int i = 1;i <= c;++ i)
	{
		if(!hs[p[i]]) {c = i-1;break;}//小剪枝:如果n个点的颜色都没有pi,那么一定选不到。
		rp[p[i]] = i;
	}
	dfs1(1,0);
	dfs2(1,1);
	Q = Read();
	for(int i = 1;i <= Q;++ i) q[i].s = Read(),q[i].t = Read(),q[i].ID = i;
	for(int i = 1;i <= m;++ i) clr[i] = 0;
	sort(q+1,q+Q+1);
	dfs3(1);
	for(int i = 1;i <= Q;++ i) Put(ans[i],'
');
	return 0;
}	

后记

我考场上没过这道题的原因是我二分之后在树上暴力跳第二部分的颜色。

Genius!

原文地址:https://www.cnblogs.com/PPLPPL/p/14663406.html