Codeforces 8题树的练习 686d,191c,842c,813c,682c,337d,782c,739b

36 篇文章 0 订阅
5 篇文章 0 订阅

背景

突然我们知道要练习树这一块的知识.8个题出来,我看了一下题目的输入输出,带白框的,必然是Codeforces.我先看A,不会.又看B,也不会,再看C,还是不会……

题目

A题 Codeforces 686D Kay and Snowflake
B题 Codeforces 191C Fools and Roads
C题 CodeForces 842C Ilya And The Tree
D题 CodeForces 813C The Tag Game
E题 CodeForces 682C Alyona and the Tree
F题 CodeForces 337D Book of Evil
G题 CodeForces 782C Andryusha and Colored Balloons
H题 CodeForces 739B Alyona and a tree
这8个题分别都是div2CDE三个题的难度.

思路及题解

T1

询问一棵树中某一棵子树的重心节点是哪一个,如果有多个,输出哪一个都可以.
首先我们需要知道重心节点的定义以及性质.
1.在一棵树中,所有点到某点的距离之和,是到重心的距离和最小.
2.把两棵树通过任何一点相连,形成的树的重心都在这两棵树的重心的连线上.
3.给一棵树添加或者删除一个节点,重心位置最多移动一格.
4.以重心为根,任何一棵子树的大小都不超过整棵树大小的一半.
由于这题直接给出了父节点的编号,可以直接搜索,方便一些.

/*
可以通过dfs直接预处理出每一棵子树的重心编号,O(1)回答每个询问.
在遍历回溯的时候相当于把u节点所在的子树连接到原来的树上,可以利用第2个性质,遍历u节点的父亲节点而找出重心.
*/
#include<bits/stdc++.h>
using namespace std;
const int boss=3e5;
vector<int> lj[boss+10];
int n,q,ans[boss+10],s[boss+10],fa[boss+10];//ans表示答案,s是包括该点在内的子节点的个数,fa表示父亲节点的编号
void dfs(int u)
{
ans[u]=u,s[u]=1;
for (int i:lj[u]) dfs(i),s[u]+=s[i];//这个冒号是c++11的用法,表示遍历stl里的每一个元素
for (int i:lj[u]) if (s[i]*2>s[u]) ans[u]=ans[i];//如果s[i]*2>s[u],表明i是u的重儿子,子树的重心一定在i和u之间
for (;(s[u]-s[ans[u]])*2>s[u];ans[u]=fa[ans[u]]);//暴力向上寻找
}
int main()
{
int i;
scanf("%d%d",&n,&q);
for (i=2;i<=n;i++) scanf("%d",&fa[i]),lj[fa[i]].push_back(i);
for (dfs(1);q--;printf("%d\n",ans[i])) scanf("%d",&i);
}

T2

给出k组询问,每一组询问表示从u节点走到v节点,按顺序输出每一条边在k组询问中被经过了多少次.
这个题是一道div.1的c题你敢信?但我不会.
去网上搜一下题解,他们都说是树链剖分.当然LCA搞一搞也能做.我又去找CF里面的AC代码看了看.
没错,树上差分+并查集.

/*
更新u,v两个节点的时候要f[u]++,f[find(v)]-=2;
把边的序号和这条边一起放到vector里.
*/
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int boss=1e5;
int fa[boss+10],ans[boss+10],f[boss+10],vis[boss+10],n,k;
vector<pii> lj[boss+10];
vector<int> fo[boss+10];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void dfs(int x)
{
fa[x]=x,vis[x]=1;
for (int i:fo[x]) f[x]++,vis[i]?f[find(i)]-=2:0;//如果这个点被搜到过,它的父节点的值-2.
for (pii i:lj[x]) if (!vis[i.first]) dfs(i.first),f[x]+=ans[i.second]=f[i.first],fa[find(i.first)]=x;//遍历子节点并回溯
}
int main()
{
int i,u,v,k;
scanf("%d",&n);
for (i=1;i<n;i++) scanf("%d%d",&u,&v),lj[u].push_back(pii(v,i)),lj[v].push_back(pii(u,i));
scanf("%d",&k);
for (i=1;i<=k;i++) 
  {
  scanf("%d%d",&u,&v);
  if (u!=v) fo[u].push_back(v),fo[v].push_back(u);//离线 
  }dfs(1);
for (i=1;i<n;i++) printf("%d ",ans[i]);
}

T3

定义一个节点u的美丽值是从根节点到u的路径上所有节点的最大公约数.
对于每个节点,你可以将它到根节点路径上的一个数字修改为0,保持剩下的数字不变,求每个节点能得到的最大美丽值.
每个节点必须分开讨论.

我好像在哪里做过这道题.群赛3吧.

/*
用n个set储存每一个点所有美丽值的可能情况,然后输出每一个点的最大值.
*/
#include<bits/stdc++.h>
using namespace std;
const int boss=2e5;
vector<int> lj[boss+10];set<int> s[boss+10];
int n,a[boss+10];
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
void dfs(int u,int fa,int r)
{
for (set<int>::iterator i=s[fa].begin();i!=s[fa].end();i++) s[u].insert(gcd(*i,a[u]));//在每一个点的所有美丽值中都有一个0
s[u].insert(r);//不把路径上的任何数字变成0
for (int i:lj[u]) if (i!=fa) dfs(i,u,gcd(r,a[u]));
}
int main()
{
int i;scanf("%d",&n);
for (i=1;i<=n;i++) scanf("%d",&a[i]);
for (i=1;i<n;i++)
  {
  int u,v;
  scanf("%d%d",&u,&v);
  lj[u].push_back(v);
  lj[v].push_back(u);
  }
s[0].insert(0),dfs(1,0,0);//注意一开始在s[0]插入了一个0
for (i=1;i<=n;i++) printf("%d ",*s[i].rbegin());
}

T4

Alice和Bob在树上玩一个游戏,Alice从1出发,Bob从x出发(x≠1),Bob先动,每一步他们可以走到一个相连的点或者不动,当Alice和Bob在同一个点时游戏结束.
Alice想要使游戏尽早结束,但Bob的想法恰好相反.输出在这种情况下游戏要进行的步数.

这题可以说是非常简单的一道题了.我居然WA了一次.

/*
分别求出Alice和Bob到树中每一个节点的距离,Alice必然会想方设法靠近Bob,而Bob走到能走的最远的点之后一直不动.
所以找出Alice到u的距离大于Bob到u的距离的点u,并算出Alice到u距离的2倍的最大值就是答案(点u就是Bob选择的不动点).
*/
#include<bits/stdc++.h>
using namespace std;
const int boss=2e5;
vector<int> lj[boss+10];
int n,x,d1[boss+10],d2[boss+10],ans;
void dfs(int u,int fa,int dep,int *d)
{
d[u]=dep;
for (int i:lj[u]) if (i!=fa) dfs(i,u,dep+1,d);
}
int main()
{
int i;
scanf("%d%d",&n,&x);
for (i=1;i<n;i++) 
  {
  int u,v;
  scanf("%d%d",&u,&v);
  lj[u].push_back(v);
  lj[v].push_back(u);
  }
dfs(1,0,0,d1),dfs(x,0,0,d2);
for (i=2;i<=n;i++) if (lj[i].size()==1&&d1[i]>d2[i]) ans=max(ans,d1[i]);//顺便多了一个条件(叶子结点).
printf("%d",ans*2);
}

T5

一棵结点和边都带权的树中,若一个结点u到它的子树中一个结点v的距离大于v的权值,则称结点u伤心.
为了使树中没有结点伤心,输出至少应该去掉多少叶子结点.

/*
明显要搜索.从节点1开始,记录从1到u的距离<=u权值的u的个数ans.答案就是n-ans.
如果一个节点被去掉了,它的子树必须也被跟着去掉,故此可知.
*/
#pragma GCC optimize("Ofast",3,"inline")
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
int read(){int x=0,f=1;char c=getchar();for (;!isdigit(c);c=getchar()) f^=c=='-';for (;isdigit(c);c=getchar()) x=x*10+c-48;return x*(f?1:-1);}
void write(int x){int bit[20],i,p=0;if (x==0) {putchar('0');return;}for (;x;x/=10) bit[++p]=x%10;for (i=p;i>0;--i) putchar(bit[i]+48);}
typedef pair<int,int> pii;const int boss=1e5;
vector<pii> lj[boss+10];int n,a[boss+10];
int dfs(int u,int fa,int dep)
{
if (dep>a[u]) return 0;
int ans=1;
for (pii i:lj[u]) if (i.first!=fa) ans+=dfs(i.first,u,max(i.second+dep,0));//因为有负权边,到某一个点距离最大的不一定是树根,所以要与0取较大值
return ans;
}
int main()
{
int i,u,v;n=read();
for (i=1;i<=n;i++) a[i]=read();
for (i=2;i<=n;i++) u=read(),v=read(),lj[i].pb(pii(u,v)),lj[u].pb(pii(i,v));
write(n-dfs(1,0,0));
}

T6

在一棵树的一个节点上有一本魔导书,可以让与这个节点距离不超过d的点受到诅咒.
给你m个(一部分)受到诅咒的点,问有多少个点可能存在魔导书.

/*
思路:找到受影响的点当中距离最远的两个点,然后扫一遍看看有多少个点离这两个点的距离都小于d,求出来的就是答案.
做法:先从1开始搜索,找到离1最远的点u;再从u开始搜索,找到离u最远的点v.这样距离最远的两个点就出来了.
然后从v开始搜索,用另一个数组储存到v的距离.最后扫一遍,解决.
*/
#pragma GCC optimize("Ofast",3,"inline")
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
int read(){int x=0,f=1;char c=getchar();for (;!isdigit(c);c=getchar()) f^=c=='-';for (;isdigit(c);c=getchar()) x=x*10+c-48;return x*(f?1:-1);}
void write(int x){int bit[20],i,p=0;if (x<0) putchar('-'),x=-x;if (x==0) {putchar('0');return;}for (;x;x/=10) bit[++p]=x%10;for (i=p;i>0;--i) putchar(bit[i]+48);}
const int boss=1e5;vector<int> lj[boss+10];
int n,m,d,p[boss+10],d1[boss+10],d2[boss+10],ans;
void dfs(int u,int fa,int dep,int *d){d[u]=dep;for (int i:lj[u]) if (i!=fa) dfs(i,u,dep+1,d);}
int main()
{
int i,u,v,rt;scanf("%d%d%d",&n,&m,&d);
for (i=0;i<m;i++) scanf("%d",&p[i]);
for (i=1;i<n;i++) scanf("%d%d",&u,&v),lj[v].pb(u),lj[u].pb(v);
dfs(1,0,0,d1),rt=0;
for (i=0;i<m;i++) if (d1[rt]<d1[p[i]]) rt=p[i];
dfs(rt,0,0,d1),rt=0;
for (i=0;i<m;i++) if (d1[rt]<d1[p[i]]) rt=p[i];
dfs(rt,0,0,d2),ans=0;
for (i=1;i<=n;i++) if (d1[i]<=d&&d2[i]<=d) ++ans;
printf("%d",ans);
}

T7

在一棵树中给所有点都染色,颜色的编号从1开始.
规定若任意选取a,b,c三个点,并且a与b,b与c之间都直接有边连接,则a,b,c三个点的颜色不能相同.
输出使用的最少颜色总数,并且输出一种可能的染色方案.

我真傻,真的.

/*
具体看代码注释
*/
#include<bits/stdc++.h>
using namespace std;
const int boss=2e5;
vector<int> lj[boss+10];set<int> s;//s是染的所有颜色的集合
int n,col[boss+10];
void dfs(int rt,int se,int fa)//现在要染色的点,我们染这个点使用的颜色,这个点的父亲节点染上的颜色
{
int a=1;
s.insert(se),col[rt]=se;
for (int v:lj[rt])  
  {
  for (;a==fa||a==se;++a);//保证子节点和该节点以及该节点的父节点染的颜色都不一样.
  if (!col[v]) dfs(v,a,se),++a;//如果子节点没有染色,就搜索子节点,并且把a+1(因为不可能有两个子节点被染上了同样的颜色).
  }
}
int main()
{
int i,u,v;
scanf("%d",&n);
for (i=1;i<n;i++)
  {
  scanf("%d%d",&u,&v);
  lj[u].push_back(v);
  lj[v].push_back(u);
  }
dfs(1,1,0),printf("%d\n",s.size());
for (i=1;i<=n;i++) printf("%d ",col[i]);
}

T8

给出一棵根节点为1的树,它的边和点都有权值,如果说u在v的子树上并且u,v间的距离<=u的权值,则称v控制u.求每个节点控制多少个节点.

/*
这题可以用差分解决.首先可以看出如果v节点能控制它的一个子节点u,则v和u之间的所有节点都能控制u.
那么我们可以二分出能控制u的离根节点最近的点,然后把u的父亲的差分值+1,二分结果的差分值-1.
最后求一下前缀和,出结果.
*/
#pragma GCC optimize("Ofast",3,"inline")
#include<bits/stdc++.h>
#define pb push_back
using namespace std;typedef long long ll;const int boss=2e5;
ll read(){ll x=0;char c=getchar();for (;!isdigit(c);c=getchar());for (;isdigit(c);c=getchar()) x=x*10+c-48;return x;}
void write(ll x){if (x<0) putchar('-'),x=-x;if (x==0) putchar(48);int bit[20],i,p=0;for (;x;x/=10) bit[++p]=x%10;for (i=p;i;--i) putchar(bit[i]+48);}
ll a[boss+10],p[boss+10],d[boss+10],pre[boss+10],lu[boss+10],res[boss+10];int n;vector<int> lj[boss+10];
void work(int u,int c,ll s)//目前搜索到的点,搜索的深度,从根节点到u的距离
{
lu[c]=u,pre[c]=s;//lu数组表示目前深度的点,pre数组表示目前深度的距离
if (c>1)
  {
  int l=1,r=c,mid;
  for (;l<r;) mid=l+r>>1,s-a[u]<=pre[mid]?r=mid:l=mid+1;
  res[p[u]]++,res[lu[r-1]]--;
  }
for (int i:lj[u]) work(i,c+1,s+d[i]);
}
void dfs(int u){for (int i:lj[u]) dfs(i),res[u]+=res[i];}
int main()
{
int i;
scanf("%d",&n);
for (i=1;i<=n;i++) scanf("%lld",&a[i]);
for (i=2;i<=n;i++) scanf("%lld%lld",&p[i],&d[i]),lj[p[i]].pb(i);
work(1,1,0),dfs(1);
for (i=1;i<=n;i++) printf("%lld ",res[i]);
}

题解就到这里,但我们对于树的研究还远没结束.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值