求树的重心
(重心:指树中的一个结点,如果将此结点删除后剩余各个连通块中点数的最大值最小。那么此结点被称为树的重心)
LINK
题目:
思路:
遍历每一结点,在此结点中判断是此结点的最大子树比较大还是非此结点子树的节点数比较大
下面用图来稍作解释:
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
vector<int>g[N];
int f[N];//以u为根的子树的结点总数(不包括根)
int n,a,b,mans=0x3f3f3f3f,manode;
void add(int x,int y){
g[x].push_back(y);
}
void dfs(int son,int fa){
f[son]=1;int maxx=0;
for(int i=0;i<g[son].size();i++){
int j=g[son][i];
if(j==fa)continue;//防止往回走
dfs(j,son);
f[son]+=f[j];//加上各子树的点
maxx=max(maxx,f[j]);//寻找以fa为根的最大子树
}
maxx=max(maxx,n-f[son]);//比较是fa的最大子树比较大还是非fa子树的节点数比较大
if(maxx<mans){//寻找最大值尽可能小
mans=maxx;manode=son;
}
}
signed main(){
cin>>n;
for(int i=1;i<n;i++){
cin>>a>>b;
add(a,b);add(b,a);
}
dfs(1,0);
for(int i=1;i<=n;i++)cout<<f[i]<<" ";cout<<endl;
cout<<manode<<" "<<mans<<endl;
return 0;
}
换根dp:
通常来说需要两次dfs,第一次从叶子到根;第二次从根到叶子。
求树的深度之和最大
(换根dp)
思路:
以1为根结点时,用dfs寻找树的深度之和,同时记录下各结点此时孩子的数量,然后dfs每点为根时运用公式(换根dp)求解;
下面用图中例子简单模拟:
代码:
1:数组
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
typedef long long ll;
struct node{
int to,next;
}e[N<<1];
int n,cnt,id;
int head[N];
ll f[N],dep[N],size[N],ans;
//size[i]:i的孩子的个数
//dep[i]:i的深度
//f[i]:以i为根的树的深度和
inline void add(int u,int v){//邻接表
e[++cnt].next=head[u];//连接的边
head[u]=cnt; //连接的下一条边
e[cnt].to=v; //这条边到达的点
}
void dfs1(int x,int fa){
size[x]=1;//每个位置初始为一个结点
dep[x]=dep[fa]+1;//子结点的深度=父节点深度+1
for(int i=head[x];i;i=e[i].next){//遍历x的子结点
int j=e[i].to ;//连接的点
if(j==fa)continue;
dfs1(j,x);
size[x]+=size[j];//加上孩子为根时所拥有的节点数
}
}
void dfs2(int x,int fa){
for(int i=head[x];i;i=e[i].next){//遍历x的子结点
int y=e[i].to;
if(y==fa)continue;
//y为x的子结点
f[y]=f[x]+n-2*size[y];//x:当前结点 y:每个儿子结点
dfs2(y,x);
}
}
signed main(){
cin>>n;
for(int i=1;i<n;i++){
int v,u;cin>>u>>v;
add(u,v); add(v,u);
}
dfs1(1,0);//以1为根寻找每个子树的结点数
for(int i=1;i<=n;i++)f[1]+=dep[i];//计算以1为根时的深度和
dfs2(1,0);
for(int i=1;i<=n;i++)
if(ans<f[i]){
ans=f[i];id=i;
}
cout<<id<<endl;
return 0;
}
2:vector:
推荐这样使用
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
typedef long long ll;
vector<int>e[N];
int n,cnt,id;
ll f[N],dep[N],size[N],ans;
//size[i]:i的孩子的个数
//dep[i]:i的深度
//f[i]:以i为根的树的深度和
void dfs1(int x,int fa){
size[x]=1;//每个位置初始为一个结点
dep[x]=dep[fa]+1;//子结点的深度=父节点深度+1
for(int i=0;i<e[x].size();i++){//遍历x的子结点
int y=e[x][i];
if(y==fa)continue;
dfs1(y,x);
size[x]+=size[y];
}
}
void dfs2(int x,int fa){
for(int i=0;i<e[x].size();i++){//遍历x的子结点
int y=e[x][i];
if(y==fa)continue;
f[y]=f[x]+n-2*size[y];
dfs2(y,x);
}
}
signed main(){
cin>>n;
for(int i=1;i<n;i++){
int v,u;cin>>u>>v;
e[u].push_back(v);e[v].push_back(u);
}
dfs1(1,0);//以1为根寻找每个子树的结点数
for(int i=1;i<=n;i++)f[1]+=dep[i];//计算以1为根时的深度和
dfs2(1,0);
for(int i=1;i<=n;i++)
if(ans<f[i]){
ans=f[i];id=i;
}
cout<<id<<endl;
return 0;
}
另一种
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
typedef long long ll;
vector<int>e[N];
int n,cnt,id;
ll f[N],dep[N],size[N],ans;
//size[i]:i的孩子的个数
//dep[i]:i的深度
//f[i]:以i为根的树的深度和
void dfs1(int x,int fa){
size[x]=1;//每个位置初始为一个结点
dep[x]=dep[fa]+1;//子结点的深度=父节点深度+1
for(auto i:e[x]){//遍历x的子结点
if(i==fa)continue;
dfs1(i,x);
size[x]+=size[i];
}
}
void dfs2(int x,int fa){
for(auto i:e[x]){//遍历x的子结点
if(i==fa)continue;
f[i]=f[x]+n-2*size[i];
dfs2(i,x);
}
}
signed main(){
cin>>n;
for(int i=1;i<n;i++){
int v,u;cin>>u>>v;
e[u].push_back(v);e[v].push_back(u);
}
dfs1(1,0);//以1为根寻找每个子树的结点数
for(int i=1;i<=n;i++)f[1]+=dep[i];//计算以1为根时的深度和
dfs2(1,0);
for(int i=1;i<=n;i++)
if(ans<f[i]){
ans=f[i];id=i;
}
cout<<id<<endl;
return 0;
}
求树的边权值最小
输出格式
一行一个整数,表示最小的不方便值。
输入输出样例
输入
5
1
1
0
0
2
1 3 1
2 3 2
3 4 3
4 5 3
输出
15
思路:
以1为树的根统计此时每个节点下的牛奶数量以及从叶子到此位置时的价值;
以下例子简单理解一下:
代码:
数组:(更快)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
struct node{
int to,next,w;
}e[N<<1];
ll n,sum,ans,cnt;
int head[N];
ll f[N],size[N],a[N];
inline void add(int u,int v,int w){
e[++cnt].next=head[u];
head[u]=cnt;
e[cnt].to=v;e[cnt].w=w;
}
void dfs1(int x,int fa){
size[x]=a[x];
for(int i=head[x];i;i=e[i].next){
int v=e[i].to ,w=e[i].w ;
if(v==fa)continue;
dfs1(v,x);
size[x]+=size[v];//统计奶牛数量
f[x]+=f[v]+w*size[v];//从x的子树到x所用的价值
}
}
void dfs2(int x,int fa){
for(int i=head[x];i;i=e[i].next ){
int y=e[i].to ,w=e[i].w ;
if(y==fa)continue;
f[y]=f[x]-w*size[y]+(sum-size[y])*w;
ans=min(ans,f[y]);
dfs2(y,x);
}
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];
for(int i=1;i<n;i++){
int u,v,w;cin>>u>>v>>w;
add(u,v,w);add(v,u,w);
}
dfs1(1,0);
ans=f[1];
dfs2(1,0);
cout<<ans<<endl;
return 0;
}
vector:(两个只是循环上的差别,速度差不多 int相对比auto快一点点)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
vector<pair<int,int>>e[N];
ll n,sum,ans;
ll f[N],size[N],a[N];
void dfs1(int x,int fa){
size[x]=a[x];
for(int i=0;i<e[x].size();i++){
int v=e[x][i].first,w=e[x][i].second;
if(v==fa)continue;
dfs1(v,x);
size[x]+=size[v];//统计奶牛数量
f[x]+=f[v]+w*size[v];//从x的子树到x所用的价值
}
}
void dfs2(int x,int fa){
for(int i=0;i<e[x].size();i++){
int y=e[x][i].first,w=e[x][i].second;
if(y==fa)continue;
f[y]=f[x]-w*size[y]+(sum-size[y])*w;
ans=min(ans,f[y]);
dfs2(y,x);
}
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];//sum:统计奶牛总数
for(int i=1;i<n;i++){
int u,v,w;cin>>u>>v>>w;
e[u].push_back({v,w});
e[v].push_back({u,w});
}
dfs1(1,0);
ans=f[1];
dfs2(1,0);
cout<<ans<<endl;
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
vector<pair<int,int>>e[N];
ll n,sum,ans;
ll f[N],size[N],a[N];
void dfs1(int x,int fa){
size[x]=a[x];
for(auto i:e[x]){
int v=i.first,w=i.second;
if(v==fa)continue;
dfs1(v,x);
size[x]+=size[v];//统计奶牛数量
f[x]+=f[v]+w*size[v];//从x的子树到x所用的价值
}
}
void dfs2(int x,int fa){
for(auto i:e[x]){
int y=i.first,w=i.second;
if(y==fa)continue;
f[y]=f[x]-w*size[y]+(sum-size[y])*w;
ans=min(ans,f[y]);
dfs2(y,x);
}
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];
for(int i=1;i<n;i++){
int u,v,w;cin>>u>>v>>w;
e[u].push_back({v,w});
e[v].push_back({u,w});
}
dfs1(1,0);
ans=f[1];
dfs2(1,0);
cout<<ans<<endl;
return 0;
}
指定树枝数量求保留树枝边权和最大
树形dp+01背包
(太菜了 这居然是简单题???)
LINK
输入输出样例
输入
5 2
1 3 1
1 4 10
2 3 20
3 5 20
输出
21
说明/提示
1
⩽
Q
<
N
⩽
100
,
每
根
树
枝
上
的
苹
果
⩽
3
×
1
0
4
1 \leqslant Q < N \leqslant 100,每根树枝上的苹果 \leqslant 3 \times10^4
1⩽Q<N⩽100,每根树枝上的苹果⩽3×104 。
思路:
题目要求以1为根,用dfs从叶子节点向上更新,利用01背包存放各点保留枝条数目的权值和;
(前提:以1为根)
f[i][j]:i的子树上保留j条边时最大权值和
注意在dp时f的[j-k-1]操作中-1是因为x与y之间也有一条边,所以后面需要加上它们之间的边权值
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e2+10;
typedef long long ll;
ll n,m;
ll f[N][N],size[N],a[N];
//size[i]:i子树的边数
vector<pair<int,int>>e[N];
void dfs(int x,int fa){
for(int i=0;i<e[x].size();i++){//遍历儿子
int y=e[x][i].first,w=e[x][i].second;
if(y==fa)continue;
dfs(y,x);//从叶子节点开始向上
size[x]+=size[y]+1;//统计边数
for(int j=min(size[x],m);j;j--){//x边的数量
for(int k=0;k<=min(size[y],j-1ll);k++)//y边的数量
// for(int k=min(size[y],j-1ll);k>=0;k--) //也可以
f[x][j]=max(f[x][j],f[x][j-k-1]+f[y][k]+w);//w:x与y之间的边权
}
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<n;i++){
int u,v,w;cin>>u>>v>>w;
e[u].push_back({v,w});
e[v].push_back({u,w});
}
dfs(1,0);
cout<<f[1][m];
return 0;
}