经典例子如下:
lca
树上差分
树的重心
树的直径
树的中心
树链剖分
目录
1.1最近公共祖先
P3379 【模板】最近公共祖先(LCA) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
简单模板,过了无数遍
P3128 [USACO15DEC] Max Flow P - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
树上差分
就差一点
#include<iostream>
using namespace std;
int n,k;
int dep[50001];
int fa[50001][21];
int a[50001];
int b[50001];
struct EDGE{
int next;
int to;
}edge[100001];
int head[50001];
int tot=0;
void addedge(int u,int v){
edge[++tot].next=head[u];
edge[tot].to=v;
head[u]=tot;
}
void dfs(int u,int father){
dep[u]=dep[father]+1;
for(int i=1;(1<<i)<=dep[u];i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;
if(v==father)continue;
fa[v][0]=u;
dfs(v,u);
}
}
void dfss(int u,int father){
cout<<"现在u,fa"<<u<<" "
//a[u]=子树a+差分b
a[u]=b[u];
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;cout<<"现在v"<<v<<endl;
if(v==father)continue;
dfs(v,u);
a[u]+=a[v];
for(int i=1;i<=n;i++){
cout<<a[i]<<" ";}
cout<<endl;
}
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--){
if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
if(x==y)return x;
}
for(int i=20;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++)head[i]=-1;
for(int i=1;i<=n-1;i++){
int u,v;
cin>>u>>v;
addedge(u,v);
addedge(v,u);
}
dfs(1,0);
while(k--){
int x,y;
cin>>x>>y;
int mf=lca(x,y);
b[x]++;
b[y]++;
b[mf]--;
if(mf != 1) b[fa[mf][0]]--;
}
cout<<"现在检验5的边";
for(int i=head[5];~i;i=edge[i].next)
{
cout<<edge[i].to<<" ";
}
cout<<"检验结束"<<endl;
dfss(1,0);
int ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,a[i]);
}
cout<<ans;
}
P5002 专心OI - 找祖先 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
n^3 tle
1.2树的几种dfs
树的重心、直径、中心无非是几种dfs的组合拳
1.2.1求sz[u]/son[u]
void dfssz(int u,int father){
sz[u]=1;
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;
if(v==father)continue;
dfssz(v,u);
sz[u]+=sz[v];
if(sz[son[u]]<sz[v])son[u]=v;
}
}
1.3树的重心
1.3.1重心性质
1、树上所有的点到树的重心的距离之和是最短的,如果有多个重心,那么总距离相等。
2、插入或删除一个点,树的重心的位置最多移动一个单位。
3、若添加一条边连接2棵树,那么新树的重心一定在原来两棵树的重心的路径上。
1.删除重心后的所有子树,节点树都不超过原树的一半。
2.一颗树最多两个重心,而且相邻。
3.树中所有的节点到重心的距离之和最小,要是有两个重心,那么他们的距离相等。
4.这颗树要是删除或者增加一条边,重心最多只移动一条边。
#include<iostream>
using namespace std;
#include<vector>
#define INF 0x3f3f3f3f
struct EDGE{
int next;
int to;
}edge[400001];
int head[20001];
int son[20001];
int sz[20001];
int tot=0;
void addedge(int u,int v){
edge[++tot].next=head[u];
edge[tot].to=v;
head[u]=tot;
}
void dfssz(int u,int father){
sz[u]=1;
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;
if(v==father)continue;
dfssz(v,u);
sz[u]+=sz[v];
if(sz[son[u]]<sz[v])son[u]=v;
}
}
int main(){
int t;
cin>>t;
while(t--){
int n;
cin>>n;
for(int i=1;i<=n;i++)head[i]=-1;
for(int i=1;i<=n-1;i++){
int u,v;cin>>u>>v;
addedge(u,v);
addedge(v,u);
}
dfssz(1,0);
int ans;int temp=INF;
for(int i=1;i<=n;i++){
if(temp>max(sz[son[i]],n-sz[i])){
temp=max(sz[son[i]],n-sz[i]);
ans=i;
}
}
cout<<ans<<" "<<temp<<endl;
}
}
1.3树的直径
法一:dfs找距离任意一点最远的点u
dfs找距离u最远的一点v
uv即为直径
只能用于没有付权边
void dfs(int u,int father){
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;
if(v==father)continue;
d[v]=d[u]+edge[i].w;
dfs(v,u);
}
}
int main(){
dfs(1,0);
//选出d[s]最大
dfs(s,0);
//选出d[v]最大
cout<<d[v];
}
法二:
某一点的最短路和次短路
某一点到其子节点的最长距离和次长距离,加起来就是直径
void dfsdp(int u,int father)
{
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;
int w=edge[i].w;
if(v==father)continue;
dfsdp(v,u);
if(d1[u]<d1[v]+w){
d2[u]=d1[u];
d1[u]=d1[v]+w;
}
else if(d2[u]<d1[v]+w){
d2[u]=d1[v]+w;
}
}
}
Cow Marathon - OpenJ_Bailian 1985 - Virtual Judge (vjudge.net.cn)
#include<iostream>
using namespace std;
int n,m;
struct EDGE{
int next;
int to;
int w;
}edge[80001];
int head[40001];
int d[40001];
int tot=0;
void addedge(int u,int v,int w){
edge[++tot].next=head[u];
edge[tot].to=v;
edge[tot].w=w;
head[u]=tot;
}
void dfs(int u,int father){
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;
if(v==father)continue;
d[v]=d[u]+edge[i].w;
dfs(v,u);
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)head[i]=-1;
for(int i=1;i<=m;i++){
int u,v,w;char s;
cin>>u>>v>>w>>s;
addedge(u,v,w);
addedge(v,u,w);
}
dfs(1,0);
int xuan1=-1;int xuan2=0;
for(int i=1;i<=n;i++){
if(d[i]>xuan2){
xuan1=i;
xuan2=d[i];
}
d[i]=0;
}
dfs(xuan1,0);
xuan1=-1;xuan2=0;
for(int i=1;i<=n;i++){
if(d[i]>xuan2){
xuan1=i;
xuan2=d[i];
}
}
cout<<xuan2;
}
树的中心
树的重心是节点数
树的重心是路径长度
到别的点的最长距离最小的点
-》枚举每个点
、子树的最长距离直径中有板子 +、向上走的
//走下面的最长
void dfsd12(int u,int father){
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;int w=edge[i].w;
if(v==father)continue;
dfsd12(v,u);
if(d1[u]<d1[v]+w){
d2[u]=d1[u];
d1[u]=d1[v]+w;
p1[u]=v;//path
}
else if(d2[u]<d1[v]+w){
d2[u]=d1[v]+w;
}
}
}
//走上面的最长
void dfsup(int u,int father){
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;int w=edge[i].w;
if(v==father)continue;
if(p1[u]==v){
up[v]=max(d2[u],up[u])+w;
}else {
up[v]=max(d1[u],up[u])+w;
}
dfsup(v,u);
}
}
//走下面的最长
void dfsd12(int u,int father){
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;int w=edge[i].w;
if(v==father)continue;
dfsd12(v,u);
if(d1[u]<d1[v]+w){
d2[u]=d1[u];
d1[u]=d1[v]+w;
p1[u]=v;//path
}
else if(d2[u]<d1[v]+w){
d2[u]=d1[v]+w;
}
}
}
//走上面的最长
void dfsup(int u,int father){
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;int w=edge[i].w;
if(v==father)continue;
if(p1[u]==v){
up[v]=max(d2[u],up[u])+w;
}else {
up[v]=max(d1[u],up[u])+w;
}
dfsup(v,u);
}
}
例题
把这的刷完
Tree Destruction - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
做出来,有顺序?
U370080 [ZOJ - 3820] Building Fire Stations - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
不会
Balancing Act - POJ 1655 - Virtual Judge (vjudge.net.cn)
简单
P1364 医院设置 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
floyd可过
状态转移?不能理解
用结论树最多两个重心,相邻