树形结构 习题

Task 1

树重心的定义:以这个点为根,那么其所有的子树的大小都不超过整个树的一半.

首先叶子节点的重心必然是自己。
考虑节点u的最重的儿子v,显然最终的答案一定是在该儿子的重心向上跳若干步(不跳到子树外)

由于重心的存在性,所以只需要满足不在该儿子且在当前子树中的节点个数不超过整个子树的一半即可.

# include <bits/stdc++.h>
using namespace std;
const int N=3e5+10;
struct rec{ int pre,to;}a[N<<1];
int head[N],fa[N],ans[N],size[N];
int n,m,tot;
void adde(int u,int v)
{
	a[++tot].pre=head[u];
	a[tot].to=v;
	head[u]=tot;
}
void dfs1(int u,int f)
{
	fa[u]=f; size[u]=1;
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==f) continue;
		dfs1(v,u); size[u]+=size[v];
	}
	if (size[u]==1) ans[u]=u;
}
void dfs2(int u,int f)
{
	if (size[u]==1) return;
	ans[u]=u; int ret=0;
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==f) continue;
		dfs2(v,u);
		if (size[v]>size[ret]) ret=v;
	}
	if (size[u]<(size[ret]<<1)) {
		int v=ans[ret];
		while (((size[u]-size[v])<<1)>size[u]) v=fa[v];
		ans[u]=v;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=2;i<=n;i++) {
		int t; scanf("%d",&t);
		adde(i,t); adde(t,i);
	}
	dfs1(1,0);

	dfs2(1,0);
	for (int i=1;i<=m;i++) {
		int x; scanf("%d",&x);
		printf("%d\n",ans[x]);
	}
	return 0;
}

Task 2

考虑一个n sqrt (n)的做法。
记录每个节点到根节点路径上约数个数信息,用前缀因数信息来更新改点答案。
仍然会少一个更新:将当前点删除,额外做一遍即可。

回溯时记得抹去信息。

但是这样并没有直接暴力+剪枝来的快...

这是一个TLE的标程

# include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct rec{ int pre,to;}a[N<<1];
int w[N],head[N],n,cnt,t[N],ans[N],tot;
map<int,int>mp;
vector<int>pp[N];
int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
void adde(int u,int v)
{
	a[++tot].pre=head[u];
	a[tot].to=v;
	head[u]=tot;
}
void dfs1(int u,int fa,int L)
{
	ans[u]=L;
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==fa) continue;
		dfs1(v,u,gcd(L,w[u]));
	}
}
void dfs2(int u,int fa,int dep)
{
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==fa) continue;
		for (int j=0;j<pp[v].size();j++) {
			mp[pp[v][j]]++;
			if (mp[pp[v][j]]>dep) ans[v]=max(ans[v],pp[v][j]);
		}
		dfs2(v,u,dep+1);
		for (int j=0;j<pp[v].size();j++) mp[pp[v][j]]--;
	}
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&w[i]);
	for (int i=1;i<=n-1;i++) {
		int u,v; scanf("%d%d",&u,&v);
		adde(u,v); adde(v,u);
	}
	dfs1(1,0,0);
	for (int i=1;i<=n;i++) {
		int x=w[i];
		for (int j=1;j<=sqrt(x);j++)
		if (x%j==0) {
			pp[i].push_back(j);
			if (x/j!=j) pp[i].push_back(x/j);
		}
	}
	for (int i=0;i<pp[1].size();i++) mp[pp[1][i]]++;
	ans[1]=max(ans[1],w[1]);
	dfs2(1,0,0);
	for (int i=1;i<=n;i++) printf("%d ",ans[i]);
	return 0;
}

然后下面是一个古怪剪枝AC的程序:

# include <bits/stdc++.h>
using namespace std;
const int N=3e5+10;
struct rec{
	int pre,to;
}a[N<<1];
int tot,n;
int ans[N],w[N],dis[N],head[N];
int gcd(int a,int b){return (b==0)?a:gcd(b,a%b);}
void adde(int u,int v)
{
	a[++tot].pre=head[u];
	a[tot].to=v;
	head[u]=tot;
}
void dfs(int u,int fa)
{
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==fa) continue;
		ans[v]=dis[v]=gcd(dis[u],w[v]);
		dfs(v,u);
	}
}
void work(int u,int fa,int d)
{
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==fa) continue;
		int t=gcd(d,w[v]);
		if (ans[v]%t==0) continue;
		ans[v]=max(ans[v],t); work(v,u,t);
	}
}
void dfs2(int u,int fa)
{
	ans[u]=max(ans[u],dis[fa]);
	work(u,fa,dis[fa]);
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==fa) continue;
		dfs2(v,u);
	}
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&w[i]);
	for (int i=2;i<=n;i++) {
		int u,v; scanf("%d%d",&u,&v);
		adde(u,v); adde(v,u);
	}
	ans[1]=dis[1]=w[1];
	dfs(1,0); dfs2(1,0);
	for (int i=1;i<=n;i++) printf("%d\n",ans[i]);
	return 0;
}

Task 3

显然 Alice 的最优路径是逐步紧逼Bob的 ,
而Bob可能向父亲方向逃,而会选择一处节点沿着该节点最长链跑。

\(c_{u}\)表示节点u以下的最长链长度(含u)

显然,满足在如下节点转而向其最长链跑是合法的。

当且仅当 \(dep_{x} - dep_{u} > dep_{u}-dep_{1}\)
对于所有合法的u点,我们需要计算两个人总操作次数的最大值。

注意到这里停在原位置不动也是一次操作,
即最大化$ 2\times (dep_u - dep_1+ c_{u}-1 ) $

# include<bits/stdc++.h>
# define int long long
using namespace std;
const int N=4e5+10;
struct rec{ int pre,to;}a[N<<1];
int n,x,head[N],tot,c[N],dep[N],f[N];
void adde(int u,int v)
{
	a[++tot].pre=head[u];
	a[tot].to=v;
	head[u]=tot;
}
void dfs(int u,int fa)
{
	int mx=0;
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==fa) continue;
		dep[v]=dep[u]+1; f[v]=u; dfs(v,u); mx=max(mx,c[v]);
	}
	c[u]=1+mx;
}
signed main()
{
	scanf("%lld%lld",&n,&x);
	for (int i=1;i<n;i++) {
		int u,v; scanf("%lld%lld",&u,&v);
		adde(u,v); adde(v,u);
	}
	dfs(1,0);
	int y=x,ans=0;
	do {
		if (dep[x]-dep[y]<dep[y]-dep[1]) 
		 ans=max(ans,2*(dep[y]-dep[1]+c[y]-1));
		y=f[y];
	}while (y!=1);
	if (dep[x]-dep[y]<dep[y]-dep[1]) ans=max(ans,2*(dep[y]-dep[1]+c[y]-1));
	printf("%lld\n",ans);
	return 0;
 }

Task 4

考虑如果一个节点被删除,为了使其为叶子节点删除,必须要把它的子树全部删除,这样对答案就是size的贡献。

在dfs过程中,一旦遇到前缀边权和小于0了,那么就从当前的子树根节点作为路径的一端,即路径长度仍然赋值成0,这就相当于记录了当前节点出发向上的最大连续子段和(贪心的选取)。

这样的复杂度是$ O(n) $ 的

# include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct rec{ int pre,to,w;}a[N<<1];
int lim[N],n,head[N],tot,ans;
void adde(int u,int v,int w)
{
	a[++tot].pre=head[u];
	a[tot].to=v;
	a[tot].w=w;
	head[u]=tot;
}
void dfs(int u,int fa,int L)
{
	if (lim[u]<L) return;
	ans++;
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==fa) continue;
		dfs(v,u,max(L+a[i].w,0));
	}
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&lim[i]);
	for (int i=2;i<=n;i++) {
		int u=i,v,w; scanf("%d%d",&v,&w);
		adde(u,v,w); adde(v,u,w);
	}
	dfs(1,0,0);
	printf("%d\n",n-ans);
	return 0;
}

Task 5

非常显然,可以要求和点集合中所有点距离都小于等于d只需要这个点和这个点集里面最远的点的距离小于等于d即可。

对于任意点到这个点集中最远点的距离一定是点集中两个相隔最远的两个点中的一个。

如果最远点不是这两个点中的一个,那么势必会导致经过最远点和另一个最远点组成的路径是一个比原来更长的直径,于魔鬼点直径相矛盾。

怎样找到相距两个最远的被标记的点。

可以使用两遍dfs即可。

然后从这两个点出发求一个dfs即可,取一个max。

然后扫一边所有点,如果答案小于等于d那么就计入答案。

复杂度\(O(n)\)

# include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct rec{
	int pre,to;
}a[N<<1];
bool mark[N];
int n,m,lim,tot,head[N],d[N];
void adde(int u,int v)
{
	a[++tot].pre=head[u];
	a[tot].to=v;
	head[u]=tot;
}
void dfs1(int u,int fa)
{
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==fa) continue;
		d[v]=d[u]+1;
		dfs1(v,u);
	}
}
void dfs2(int u,int fa,int step)
{
	d[u]=max(d[u],step);
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==fa) continue;
		dfs2(v,u,step+1);
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&lim);
	memset(mark,false,sizeof(mark));
	for (int i=1;i<=m;i++) {
		int t; scanf("%d",&t); mark[t]=true;
	}
	for (int i=2;i<=n;i++) {
		int u,v; scanf("%d%d",&u,&v);
		adde(u,v); adde(v,u);
	}
	memset(d,0,sizeof(d)); d[0]=-1;
	dfs1(1,0); int pt1=0; 
	for (int i=1;i<=n;i++) if ((d[i]>d[pt1])&&mark[i]) pt1=i;
	memset(d,0,sizeof(d)); d[0]=-1;
	dfs1(pt1,0);int pt2=0;
	for (int i=1;i<=n;i++) if  ((d[i]>d[pt2])&&mark[i]) pt2=i;
	memset(d,0,sizeof(d)); d[0]=-1;
	dfs2(pt1,0,0);dfs2(pt2,0,0);
	int ans=0;
	for (int i=1;i<=n;i++) if (d[i]<=lim) ans++;
	printf("%d\n",ans);
	return 0;
}

Task 6

非常显然会有两个特殊情况出现,即

  • n = 1 时答案为 1 (按照链形染色)
  • n = 2 时答案为 2 (按照链形染色)
  • n > 2 时答案下限为 3

染色时,钦定1节点为1,然后从1节点开始,遍历到每一个节点的时候把该节点的儿子染色(参考当前点和当前点父亲的颜色,把儿子不重复地编号)

# include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct rec{
	int pre,to;
}a[N<<1];
int head[N],tot,n,k,col[N]; 
void adde(int u,int v)
{
	a[++tot].pre=head[u];
	a[tot].to=v;
	head[u]=tot;
}
void dfs(int u,int fa)
{
	int num=1;
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==fa) continue;
		while (col[u]==num||col[fa]==num) num++;
		col[v]=num++;  k=max(k,col[v]);
		dfs(v,u);
	}
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<n;i++) {
		int u,v; scanf("%d%d",&u,&v);
		adde(u,v); adde(v,u);
	}
	col[1]=1; dfs(1,0);
	printf("%d\n",k);
	for (int i=1;i<=n;i++) printf("%d ",col[i]);
	puts("");
	return  0;
}

Task 7

任取 root = 1
考虑每一个点对这个点到根节点答案的贡献。

由于边权\(w > 0\) 所以前缀和具有单调性,可以倍增找到某一个临界点\(x\)恰到当前点\(u\)的距离小于等于\(a_v\),那么必然,\(x\)\(father(u)\)链上的所有点都会符合答案。

考虑维护一个只有路径加法,末尾询问每个节点的具体数值。

可以采用树上差分解决。

维护点权路径加法,对于一条\(u,v\)的链操作,那么就\(d_u +=1\) , \(d_v +=1\) , \(d_{lca(u,v)} -=1 , d_{father(lca(u,v))} -=1\).

然后最终直接跑子树和,就是原树。

复杂度\(O(n \ log \ n)\)

# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N=2e5+10;
struct rec{
	int pre,to,w;
}a[N<<1];
int tot,n;
int head[N],c[N],g[N<<1][20],d[N<<1][20],w[N];
void adde(int u,int v,int w)
{
	a[++tot].pre=head[u];
	a[tot].to=v;
	a[tot].w=w;
	head[u]=tot;
}
void dfs(int u,int fa)
{
	g[u][0]=fa;
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==fa) continue;
		d[v][0]=a[i].w; dfs(v,u);
	}
}
void init()
{
	dfs(1,0);
	for (int i=1;i<=18;i++)
	 for (int j=1;j<=n;j++)
	  g[j][i]=g[g[j][i-1]][i-1],
	  d[j][i]=d[j][i-1]+d[g[j][i-1]][i-1];
}
int jump(int u,int sum)
{
	for (int i=18;i>=0;i--)
	 if (d[u][i]<=sum&&g[u][i]!=0) { sum-=d[u][i]; u=g[u][i];}
	return u;  
}
void dfs2(int u,int fa)
{
	for (int i=head[u];i;i=a[i].pre) {
		int v=a[i].to; if (v==fa) continue;
		dfs2(v,u); c[u]+=c[v];
	}
}
signed main()
{
	scanf("%lld",&n);
	for (int i=1;i<=n;i++) scanf("%lld",&w[i]);
	for (int i=2;i<=n;i++) {
		int u=i,v,w; scanf("%lld%lld",&v,&w);
		adde(u,v,w); adde(v,u,w);
	}
	init();
	for (int i=1;i<=n;i++) {
		int to=jump(i,w[i]); 
		c[g[to][0]]--; c[g[i][0]]++;
	}
	dfs2(1,0);
	for (int i=1;i<=n;i++) printf("%lld ",c[i]); puts("");
	return 0;
}
posted @ 2019-08-02 22:10  ljc20020730  阅读(291)  评论(0编辑  收藏  举报