P5836 [USACO19DEC]Milk Visits S
很巧妙的思路
洛谷题解
P3379 【模板】最近公共祖先(LCA)
思路
Pecco的知乎文章
讲的很好!
实现
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int e[50010<<1],ne[50010<<1];
int head[500010], tot;
void add(int x, int y) {
e[++tot] = y;
ne[tot] = head[x];
head[x] = tot;
}
int depth[500001], fa[500001][22], lg[500001];
//预处理出fa数组
void dfs(int now, int fath) {
fa[now][0] = fath;
depth[now] = depth[fath] + 1;
for(int i = 1; i <= lg[depth[now]]; ++i)
fa[now][i] = fa[fa[now][i-1]][i-1];
for(int i = head[now]; i; i = ne[i])
if(e[i]!= fath) dfs(e[i], now);
}
int LCA(int x, int y) {
if(depth[x] < depth[y])
swap(x, y);
while(depth[x] > depth[y])
x = fa[x][lg[depth[x]-depth[y]] - 1];
if(x == y)
return x;
for(int k = lg[depth[x]] - 1; k >= 0; --k)
if(fa[x][k] != fa[y][k])
x = fa[x][k], y = fa[y][k];
return fa[x][0];
}
int main()
{
int n, m, s; scanf("%d%d%d", &n, &m, &s);
for(int i = 1; i <= n-1; ++i) {
int x, y; scanf("%d%d", &x, &y);
add(x, y); add(y, x); //建图的时候双向
}
for(int i = 1; i <= n; ++i)
lg[i] = lg[i-1] + (1 << lg[i-1] == i); //预处理lg数组
dfs(s, 0);
for(int i = 1; i <= m; ++i) {
int x, y; scanf("%d%d",&x, &y);
printf("%d\n", LCA(x, y));
}
return 0;
}
P1395 会议
思路
树的重心:对于树上的每一个点,计算其所有子树中最大的子树节点数,这个值最小的点就是这棵树的重心。
(这里以及下文中的“子树”都是指无根树的子树,即包括“向上”的那棵子树,并且不包括整棵树自身。)
求树的重心:
- 在 DFS 中计算每个子树的大小,记录“向下”的子树的最大大小,利用总点数 - 当前子树(这里的子树指有根树的子树)的大小得到“向上”的子树的大小,然后就可以依据定义找到重心了。
性质:
-
以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。
-
树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。
-
把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
-
在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。
由第二条性质可知本题只需要dfs求出树的重心,再bfs求出所有点到重心的距离和。
实现
//来自洛谷题解区 加勒比·史努比
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
const int N=50010;
int n,ans;
int maxn=10000000;
int u,r,sum;
int d[N],dis[N];
vector<int> G[N];
queue<int> q;
bool vis[N];
void dfs(int s,int f){ //求树的重心
d[s]=1;
int res=0;
for(int i=0;i<G[s].size();i++){
if(G[s][i]==f) continue;
dfs(G[s][i],s);
d[s]+=d[G[s][i]];
res=max(res,d[G[s][i]]);
}
res=max(res,n-d[s]);
if(res<maxn||(res==maxn&&ans>s)){
maxn=res;
ans=s;
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n-1;i++){
scanf("%d%d",&u,&r);
G[u].push_back(r);
G[r].push_back(u);
}
dfs(1,0);
q.push(ans);
while(!q.empty()){ //BFS
int e=q.front();
q.pop();
vis[e]=true;
sum+=dis[e];
for(int i=0;i<G[e].size();i++){
if(!vis[G[e][i]]){
q.push(G[e][i]);
dis[G[e][i]]=dis[e]+1;
}
}
}
printf("%d %d",ans,sum);
return 0;
}
P5536 【XR-3】核心城市
思路
k个点中必然有树的直径的中点,两次dfs求树的直径,第三次dfs求出以直径中点为根,其他节点的深度和从该节点出发最远能到达的节点深度,把其他节点按照最大深度-这个点的深度降序排序,前k个成为中心城市。第k+1个城市的最大深度-这个点的深度+1即为答案。
(因为最大深度-这个点的深度就是该点到叶结点的距离,该点是第k+1个点,有k个中心城市,那么这个点一定和中心城市直接相连,+1即可)
实现
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define N 100010
int n,k,zj,num,ans_k;
int cut,head[N],ver[2*N],ne[2*N];
int deep[N],f[N],maxdeep[N],ans[N];
bool cmp(int a,int b){
return a>b;
}
void add(int x,int y){
ver[++cut]=y;ne[cut]=head[x];head[x]=cut;
}
//求直径
void dfs1(int x,int fa){
if(deep[x]>zj){
zj=deep[x];
num=x;
}
for(int i=head[x];i;i=ne[i]){
int y=ver[i];
if(y==fa)continue;
deep[y]=deep[x]+1;
dfs1(y,x);
}
}
void dfs2(int x,int fa){
if(deep[x]>zj){
zj=deep[x];
num=x;
}
for(int i=head[x];i;i=ne[i]){
int y=ver[i];
if(y==fa)continue;
deep[y]=deep[x]+1;
f[y]=x;
dfs2(y,x);
}
}
//
void dfs_k(int x,int fa){
maxdeep[x]=deep[x];
for(int i=head[x];i;i=ne[i]){
int y=ver[i];
if(y==fa)continue;
deep[y]=deep[x]+1;
dfs_k(y,x);
maxdeep[x]=max(maxdeep[x],maxdeep[y]);
}
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<n;++i){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
//直径
dfs1(1,0);
memset(deep,0,sizeof(deep));
zj=0;
dfs2(num,0);
//
int kkk=num;
//找直径的中点
for(int i=1;i<=(deep[num]+1)/2;++i)kkk=f[kkk];
memset(deep,0,sizeof(deep));
//再搜一次
dfs_k(kkk,0);
for(int i=1;i<=n;++i)ans[i]=maxdeep[i]-deep[i];
sort(ans+1,ans+n+1,cmp);
printf("%d\n",ans[k+1]+1);
return 0;
}
欢迎指正-