newcoder NOIP提高组模拟赛C题——保护

我是发了疯才来写这道题的

我如果用写这道题的时间去写dp,我估计我能写上三四道

可怕的数据结构题

题目

这道题的鬼畜之处在于实在是不太好写

我们看到要求离树根尽量的近,所以我们很容易就能想到树上倍增,所以我们需要有一种能快速求出一条路径能被多少条给出路径完全覆盖

我们知道起点是固定的,要求完全覆盖的话我们必须要保证给定的路径的一个端点在起点的子树里,同时还要求另一个端点在路径的终点的外部,也就是说路径的(LCA)深度小于等于终点

于是这样就可以写一个还算可观的(40)分暴力了

这是考场上想出主席树没敢打的40分暴力,核心思想就是在起点的子树里找路径的端点,之后判断这些端点所对应的路径的(LCA)的深度

复杂度是(O(qnlogn))

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#define max(a,b) ((a)>(b)?(a):(b))
#define re register
#define maxn 50005
inline int read()
{
	char c=getchar();
	int x=0;
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9')
	  x=(x<<3)+(x<<1)+c-48,c=getchar();
	return x;
}
int head[maxn],deep[maxn],fa[maxn],maxdep=1;
std::vector<int> v[maxn];
struct node
{
	int v,nxt;
}e[maxn<<1];
int num,H;
inline void add_edge(int x,int y)
{
	e[++num].v=y;
	e[num].nxt=head[x];
	head[x]=num;
}
int lca[maxn];
int f[maxn][20];
int tot,n,m,X[maxn],Y[maxn];
void dfs(int x)
{
	for(re int i=head[x];i;i=e[i].nxt)
	if(!deep[e[i].v])
	{
		f[e[i].v][0]=x;
		deep[e[i].v]=deep[x]+1;
		dfs(e[i].v);
	}
}
inline int LCA(int x,int y)
{
	if(deep[x]<deep[y]) std::swap(x,y);
	for(re int i=H;i>=0;i--)
	if(deep[f[x][i]]>=deep[y]) x=f[x][i];
	if(x==y) return x;
	for(re int i=H;i>=0;i--)
	if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return f[x][0];
}
inline int check(int x,int now)
{
	int ans=0;
	for(re int i=0;i<v[x].size();i++) if(deep[v[x][i]]<=now) ans++;
	for(re int i=head[x];i;i=e[i].nxt)
	if(deep[e[i].v]>deep[x]) ans+=check(e[i].v,now);
	return ans;
}
int main()
{
	n=read(),m=read();
	int x,y;
	for(re int i=1;i<n;i++)
	{
		x=read(),y=read();
		add_edge(x,y),add_edge(y,x);
	}
	deep[1]=1;
	dfs(1);
	H=log2(n);
	for(re int i=1;i<=H;i++)
		for(re int j=1;j<=n;j++)
			f[j][i]=f[f[j][i-1]][i-1];
	for(re int i=1;i<=m;i++)
	{
		X[i]=read(),Y[i]=read();
		if(deep[X[i]]>deep[Y[i]]) std::swap(X[i],Y[i]);
		lca[i]=LCA(X[i],Y[i]);
		v[Y[i]].push_back(lca[i]);
		if(X[i]!=Y[i]) v[X[i]].push_back(lca[i]);
	}
	int Q=read(),k;
	while(Q--)
	{
		x=read(),k=read();
		int sx=x;
		for(re int i=H;i>=0;i--)
			if(f[x][i]&&check(sx,deep[f[x][i]])>=k) x=f[x][i];
		printf("%d
",deep[sx]-deep[x]);
	}
	return 0;
}

这根正解其实很接近了,我们想要快速判断一个答案是否可行的话,我们可以利用主席树来做

这里的主席树需要解决子树内的问题,所以还是按照(dfs)序来建主席树,之后主席树里的权值是这个路径端点对应的(LCA)的深度

之后我们就可以利用主席树差分知道一个子树内的所有端点对应的(LCA)的深度小于等于某个值得有多少个了

所以就可以倍增加上主席树判断,时间复杂度(O(nlogn+qlog^2n))

主要是太难写了,考场上想出正解也不敢写

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
#define re register
#define maxn 200005
#define max(a,b) ((a)>(b)?(a):(b))
inline int read()
{
	char c=getchar();
	int x=0;
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9')
	  x=(x<<3)+(x<<1)+c-48,c=getchar();
	return x;
}
int head[maxn],deep[maxn],maxdep=1;
int sum1[maxn],X[maxn],Y[maxn];
struct node
{
	int v,nxt;
}e[maxn<<1];
int num;
inline void add_edge(int x,int y)
{
	e[++num].v=y;
	e[num].nxt=head[x];
	head[x]=num;
}
std::vector<int> v[maxn];
int sum[maxn],to[maxn],_to[maxn];
//_to[i]是将序列上的点i映射到序列上,to[i]是将树上的点映射到序列上
int top[maxn],son[maxn],lca[maxn];
int f[maxn][19];
int tot;
void dfs(int x)
{
	_to[++tot]=x;
	to[x]=tot;
	int maxx=-1;
	sum1[x]=1;
	for(re int i=head[x];i;i=e[i].nxt)
	if(!deep[e[i].v])
	{
		f[e[i].v][0]=x;
		deep[e[i].v]=deep[x]+1;
		maxdep=max(maxdep,deep[e[i].v]);
		dfs(e[i].v);
		sum1[x]+=sum1[e[i].v];
		if(sum1[e[i].v]>maxx) maxx=sum1[e[i].v],son[x]=e[i].v;
	}
}
void dfs2(int x,int topf)
{
	top[x]=topf;
	if(!son[x]) return;
	dfs2(son[x],topf);
	for(re int i=head[x];i;i=e[i].nxt)
	if(deep[e[i].v]>deep[x]&&e[i].v!=son[x]) dfs2(e[i].v,e[i].v);
}
inline int LCA(int x,int y)
{
	while(top[x]!=top[y])
	{
		if(deep[top[x]]<deep[top[y]]) std::swap(x,y);
		x=f[top[x]][0];
	}
	if(deep[x]>deep[y]) return y;
	return x;
}
int l[maxn<<6],r[maxn<<6],d[maxn<<6];
int rt[maxn];
int n,m,cnt;
int Build(int x,int y)
{
	int root=++cnt;
	int mid=x+y>>1;
	if(x==y) return root;
	l[root]=Build(x,mid);
	r[root]=Build(mid+1,y);
	return root;
}
int change(int pre,int x,int y,int t)
{
	int root=++cnt;
	d[root]=d[pre]+1;
	if(x==y) return root;
	l[root]=l[pre];
	r[root]=r[pre];
	int mid=x+y>>1;
	if(t<=mid) l[root]=change(l[pre],x,mid,t);
	else r[root]=change(r[pre],mid+1,y,t);
	return root;
}
inline int query(int pre,int x,int y,int xx,int yy)
{
	if(xx<=x&&yy>=y) return d[pre];
	int mid=x+y>>1;
	if(yy<=mid) return query(l[pre],x,mid,xx,yy);
	if(xx>mid) return query(r[pre],mid+1,y,xx,yy);
	return  query(l[pre],x,mid,xx,yy)+query(r[pre],mid+1,y,xx,yy);
}//主席树的板子
int main()
{
	n=read(),m=read();
	int x,y;
	for(re int i=1;i<n;i++)
	{
		x=read(),y=read();
		add_edge(x,y),add_edge(y,x);
	}
	deep[1]=1;
	dfs(1);
	int H=log2(maxdep)+1;
	dfs2(1,1);
	for(re int i=1;i<=H;i++)
		for(re int j=1;j<=n;j++)
			f[j][i]=f[f[j][i-1]][i-1];
	for(re int i=1;i<=m;i++)
	{
		X[i]=read(),Y[i]=read();
		lca[i]=LCA(X[i],Y[i]);
		v[Y[i]].push_back(lca[i]),sum[Y[i]]++;
		if(X[i]!=Y[i]) v[X[i]].push_back(lca[i]),sum[X[i]]++;
        //一条路径正反算两次,如果是同一个点就只算一次
	}
	rt[0]=Build(1,maxdep);
	int pre=0;
	for(re int i=1;i<=n;i++)
	{
		if(!sum[_to[i]]) 
		{
			rt[i]=rt[i-1];
			continue;
		}
		int T=rt[i-1];
		for(re int j=0;j<v[_to[i]].size();j++)
			T=change(T,1,maxdep,deep[v[_to[i]][j]]);
        //一个点可能是多条路径的端点
		rt[i]=T;
	}
	int Q=read(),k;
	while(Q--)
	{
		x=read(),k=read();
		int sx=x;
		for(re int i=H;i>=0;i--)
		{
			if(!f[x][i]) continue; 
 			int mid=query(rt[to[sx]+sum1[sx]-1],1,maxdep,1,deep[f[x][i]]);
			int MID=-query(rt[to[sx]-1],1,maxdep,1,deep[f[x][i]]);
			if(f[x][i]&&mid+MID>=k)
			x=f[x][i];
		}
		printf("%d
",deep[sx]-deep[x]);
	}
	return 0;
}

newcoder的机子好像又变慢了,这个代码好像又会被卡一个点

但是正解的线段树合并我不会写啊

不过优化一下常数就又能过了

原文地址:https://www.cnblogs.com/asuldb/p/10207865.html