P5908 猫猫和企鹅
题目描述
王国里有 n 个居住区,它们之间有 n−1 条道路相连,并且保证从每个居住区出发都可以到达任何一个居住区,并且每条道路的长度都为 1。
除 1 号居住区外,每个居住区住着一个小企鹅,有一天一只猫猫从 1 号居住区出发,想要去拜访一些小企鹅。可是猫猫非常的懒,它只愿意去距离它不大于 d 的小企鹅们。
猫猫非常的懒,因此希望你告诉他,他可以拜访多少只小企鹅。
输入格式
第一行两个整数 n,d,意义如题所述。
第二行开始,共 n−1 行,每行两个整数 u,v,表示居民区 u 和 v 之间存在道路。
输出格式
一行一个整数,表示猫猫可以拜访多少只小企鹅。
输入输出样例
输入 #1复制
5 1
1 2
1 3
2 4
3 5
输出 #1复制
2
说明/提示
对于 100% 的数据,满足 1≤n,d≤105,保证所有居民区从 1 开始标号。
实现代码:
cpp
#include<bits/stdc++.h>
using namespace std;
vector <int> g[123456];
bool vis[123456];
int ans,n,d;
void dfs(int now,int dis)
{
vis[now]=1;
if(dis==d) return;
for(int i=0;i<g[now].size();i++)
{
if(!vis[g[now][i]])
{
dfs(g[now][i],dis+1);
ans++;
}
}
}
int main()
{
cin>>n>>d;
for(int i=1;i<n;i++)
{
int a,b;
cin>>a>>b;
g[a].push_back(b);
g[b].push_back(a);
}
dfs(1,0);
cout<<ans;
return 0;
}
P1099 [NOIP 2007 提高组] 树网的核
题目描述
设 T=(V,E,W) 是一个无圈且连通的无向图(也称为无根树),每条边都有正整数的权,我们称 T 为树网(treenetwork),其中 V,E 分别表示结点与边的集合,W 表示各边长度的集合,并设 T 有 n 个结点。
路径:树网中任何两结点 a,b 都存在唯一的一条简单路径,用 d(a,b) 表示以 a,b 为端点的路径的长度,它是该路径上各边长度之和。我们称 d(a,b) 为 a,b 两结点间的距离。
D(v,P)=min{d(v,u)}, u 为路径 P 上的结点。
树网的直径:树网中最长的路径称为树网的直径。对于给定的树网 T,直径不一定是唯一的,但可以证明:各直径的中点(不一定恰好是某个结点,可能在某条边的内部)是唯一的,我们称该点为树网的中心。
偏心距 ECC(F):树网 T 中距路径 F 最远的结点到路径 F 的距离,即
ECC(F)=max{D(v,F),v∈V}
任务:对于给定的树网 T=(V,E,W) 和非负整数 s,求一个路径 F,他是某直径上的一段路径(该路径两端均为树网中的结点),其长度不超过 s(可以等于 s),使偏心距 ECC(F) 最小。我们称这个路径为树网 T=(V,E,W) 的核(Core)。必要时,F 可以退化为某个结点。一般来说,在上述定义下,核不一定只有一个,但最小偏心距是唯一的。
下面的图给出了树网的一个实例。图中,A−B 与 A−C 是两条直径,长度均为 20。点 W 是树网的中心,EF 边的长度为 5。如果指定 s=11,则树网的核为路径DEFG(也可以取为路径DEF),偏心距为 8。如果指定 s=0(或 s=1、s=2),则树网的核为结点 F,偏心距为 12。

输入格式
共 n 行。
第 1 行,两个正整数 n 和 s,中间用一个空格隔开。其中 n 为树网结点的个数,s 为树网的核的长度的上界。设结点编号以此为 1,2...,n。
从第 2 行到第 n 行,每行给出 3 个用空格隔开的正整数 u,v,w,依次表示每一条边的两个端点编号和长度。例如,2 4 7 表示连接结点 2 与 4 的边的长度为 7。
输出格式
一个非负整数,为指定意义下的最小偏心距。
输入输出样例
输入 #1复制
5 2
1 2 5
2 3 2
2 4 4
2 5 3
输出 #1复制
5
输入 #2复制
8 6
1 3 2
2 3 2
3 4 6
4 5 3
4 6 4
4 7 2
7 8 3
输出 #2复制
5
说明/提示
- 对于 40% 的数据,保证 n≤15。
- 对于 70% 的数据,保证 n≤80。
- 对于 100% 的数据,保证 2≤n≤300,0≤s≤103,1≤u,v≤n,0≤w≤103。
NOIP2007 提高组第四题
实现代码:
cpp
#include <cstdio>
#include <map>
#include <vector>
#include <algorithm>
#define mp make_pair
#define pr pair<int,int>
#define maxn 500010
#define ll long long
using namespace std;
struct ed{
ll u,nex,w;
}e[maxn*2];
ll n,s,st,fir[maxn];
ll ans,A,B,D,l,r,d[maxn];
ll f[maxn][21],dd[maxn],v[maxn],w[maxn],F,minn;
ll p,q,l1,l2;
void add(ll x,ll y,ll w)
{
e[++st].u=y; e[st].nex=fir[x]; e[fir[x]=st].w=w;
e[++st].u=x; e[st].nex=fir[y]; e[fir[y]=st].w=w;
}
void dfs(ll u,ll fa,ll w)
{
d[u]=d[fa]+w; if (d[u]>ans) ans=d[u],l=u;
for (ll i=fir[u],ax=0;i;i=e[i].nex)
{
ll v=e[i].u,w=e[i].w;
if (v==fa) continue; dfs(v,u,w);
}
}
void dfsl(ll u,ll fa)
{
f[u][0]=fa; dd[u]=dd[fa]+1;
for (ll i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1];
for (ll i=fir[u];i;i=e[i].nex) if (e[i].u!=fa) dfsl(e[i].u,u),w[e[i].u]=e[i].w;
}
void Fdis(ll u) { while (u) v[u]=1,u=f[u][0]; }
ll lca(ll x,ll y)
{
if (dd[x]>dd[y]) swap(x,y);
for (ll i=20;i>=0;i--)
if (dd[y]-(1<<i)>=dd[x]) y=f[y][i];
if (x==y) return x;
for (ll i=20;i>=0;i--)
if (f[y][i]!=f[x][i]) y=f[y][i],x=f[x][i];
return f[x][0];
}
ll dismin()
{
dfsl(A,0); Fdis(B);
for (ll i=1;i<=n;i++)
if (!v[i])
{
ll L=lca(i,B);
minn=max(minn,d[i]-d[L]);
}
return minn;
}
ll drop(ll u,ll fa,ll x)
{
for (ll i=fir[u];i;i=e[i].nex)
if (v[e[i].u]) {
if (e[i].u==fa) continue;
l1+=e[i].w; if (l1>x) return u;
else return drop(e[i].u,u,x);
}
}
ll up(ll u,ll x)
{
while (u!=A&&l2+w[u]<=x) l2+=w[u],u=f[u][0];
return u;
}
bool check(ll x)
{
l1=l2=0; p=drop(A,0,x); q=up(B,x);
return (d[q]-d[p]<=s);
}
int main()
{
scanf("%lld%lld",&n,&s);
for (ll i=1,x,y,w;i<n;i++)
scanf("%lld%lld%lld",&x,&y,&w),add(x,y,w);
dfs(1,0,0); ans=0; r=l; dfs(l,0,0);
A=r; B=l; D=ans;
l=dismin(); r=D;
while (l<r)
{
ll mid=(l+r)/2;
if (check(mid)) r=mid; else l=mid+1;
}
printf("%lld\n",l);
}
P1395 会议
题目描述
有一个村庄居住着 n 个村民,有 n−1 条路径使得这 n 个村民的家联通,每条路径的长度都为 1。现在村长希望在某个村民家中召开一场会议,村长希望所有村民到会议地点的距离之和最小,那么村长应该要把会议地点设置在哪个村民的家中,并且这个距离总和最小是多少?若有多个节点都满足条件,则选择节点编号最小的那个点。
输入格式
第一行,一个数 n,表示有 n 个村民。
接下来 n−1 行,每行两个数字 a 和 b,表示村民 a 的家和村民 b 的家之间存在一条路径。
输出格式
一行输出两个数字 x 和 y。
x 表示村长将会在哪个村民家中举办会议。
y 表示距离之和的最小值。
输入输出样例
输入 #1复制
4
1 2
2 3
3 4
输出 #1复制
2 4
说明/提示
数据范围
对于 70% 数据 n≤103。
对于 100% 数据 n≤5×104。
实现代码:
cpp
#include<cstdio>
#define N 50005
int d[N];
int f[N];
int n,cnt;
int size[N];
bool vis[N];
int head[N];
struct Edge{
int to,nxt;
}edge[N<<1];
void add(int x,int y){
edge[++cnt].to=y;
edge[cnt].nxt=head[x];
head[x]=cnt;
}
void dfs1(int now){
size[now]=1;
for(int i=head[now];i;i=edge[i].nxt){
int to=edge[i].to;
if(d[to]) continue;
d[to]=d[now]+1;
dfs1(to);
size[now]+=size[to];
}
}
void dfs(int now,int fa){
f[now]=f[fa]+n-2*size[now];
for(int i=head[now];i;i=edge[i].nxt){
int to=edge[i].to;
if(to==fa) continue;
dfs(to,now);
}
}
signed main(){
scanf("%d",&n);
for(int x,y,i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
d[1]=1;
dfs1(1);
int maxn=0,idx=1;
for(int i=1;i<=n;i++) maxn+=d[i];
maxn-=n;
f[1]=maxn;
for(int i=head[1];i;i=edge[i].nxt){
int to=edge[i].to;
dfs(to,1);
}
for(int i=2;i<=n;i++){
if(f[i]<maxn) maxn=f[i],idx=i;
}
printf("%d %d",idx,maxn);
return 0;
}
P3379 【模板】最近公共祖先(LCA)
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数 N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N−1 行每行包含两个正整数 x,y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 M 行每行包含两个正整数 a,b,表示询问 a 结点和 b 结点的最近公共祖先。
输出格式
输出包含 M 行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
输入 #1复制
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
输出 #1复制
4
4
1
4
4
说明/提示
对于 30% 的数据,N≤10,M≤10。
对于 70% 的数据,N≤10000,M≤10000。
对于 100% 的数据,1≤N,M≤5×105,1≤x,y,a,b≤N,不保证 a=b。
样例说明:
该树结构如下:

第一次询问:2,4 的最近公共祖先,故为 4。
第二次询问:3,2 的最近公共祖先,故为 4。
第三次询问:3,5 的最近公共祖先,故为 1。
第四次询问:1,2 的最近公共祖先,故为 4。
第五次询问:4,5 的最近公共祖先,故为 4。
故输出依次为 4,4,1,4,4。
2021/10/4 数据更新 @fstqwq:应要求加了两组数据卡掉了暴力跳。
实现代码:
cpp
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 5e5 + 5;
int n, m, R, dn, dfn[N], mi[19][N];
vector<int> e[N];
int get(int x, int y) {return dfn[x] < dfn[y] ? x : y;}
void dfs(int id, int f) {
mi[0][dfn[id] = ++dn] = f;
for(int it : e[id]) if(it != f) dfs(it, id);
}
int lca(int u, int v) {
if(u == v) return u;
if((u = dfn[u]) > (v = dfn[v])) swap(u, v);
int d = __lg(v - u++);
return get(mi[d][u], mi[d][v - (1 << d) + 1]);
}
int main() {
scanf("%d %d %d", &n, &m, &R);
for(int i = 2, u, v; i <= n; i++) {
scanf("%d %d", &u, &v);
e[u].push_back(v), e[v].push_back(u);
}
dfs(R, 0);
for(int i = 1; i <= __lg(n); i++)
for(int j = 1; j + (1 << i) - 1 <= n; j++)
mi[i][j] = get(mi[i - 1][j], mi[i - 1][j + (1 << i - 1)]);
for(int i = 1, u, v; i <= m; i++) scanf("%d %d", &u, &v), printf("%d\n", lca(u, v));
return 0;
}