树上dp的一些整理(长期施工中)

 

这一部分应该算是树上问题的入门 通过dfs以及转移方程解决问题 

树就相当于没有环的图 最顶端的点叫做根节点 一般树由上往下长,根节点向下分成若干个子节点。最底下的子节点称为叶子节点。树有很多非常好的性质值得学习。

在解决树的问题的时候 如果没有规定root 我们就以1为root,在存图的时候,一般存双向边比较方便。下文中所有边均存入名为adj的vector中,即邻接表。

1.基础问题:子树大小、子树最值等

在解决这些问题的时候 我们发现他们有个共同特点 就是父节点的信息能由子节点推算出来

也就是说,父亲节点的子树大小,其实就等于它的所有子节点的子树大小相加再+1。我们可以利用dfs,先递归到叶子节点,再一层一层向上推,此时跑一遍的时间复杂度为O(n)

以下为求dfs求子树大小 最值同理 将转移方程改变为sum[u]=max(sum[u],sum[v])即可

复制代码
int sum[MAXN];
void dfs_sum(int u,int fa){
    sum[u]=1;//规定初始的值为1 
    for(auto &v:adj[u]){
        if(v==fa) continue;//如果是父亲节点 就不搜 
        dfs_sum(u,v);//向下递归 
        sum[u]+=sum[v];//转移 
    }
}
View Code
复制代码

稍微难一点的是求树上最长路径 在求路径的时候要维护最大值和次大值 

复制代码
ll dp[2][MAXN] 
void dfs(int u,int fa){
    for(auto &[v,w]:adj[u]){
        if(v==fa) continue;
        dfs(v,u);
        if(dp[0][v]+w>dp[0][u) dp[1][u]=dp[0][u],dp[0][u]=dp[0][v]+w;
        else if(dp[0][v]+w>dp[1][u]) dp[1][u]=dp[0][v]+w;
        //更新最大值和次大值 
    }
    ans=max(ans,dp[0][u]+dp[1][u]);
}
View Code
复制代码

 

2.双向遍历

双向遍历就是子节点信息由父亲节点得到,比如经典问题,求树的中心 树上某点到其他任意点的最大值最小化。我们不仅需要知道向下的值 还需要知道向上的值

复制代码
vector<pair<int,int> > adj[MAXN];
ll up1[MAXN],up2[MAXN],down[MAXN];
void dfs_down(int u,int fa) {
    up1[u]=0,up2[u]=0;
    for(auto [v,w]:adj[u]) { //往下寻找下面的最大值
        if(v==fa) continue;
        dfs_down(v,u);
        if(up1[v]+w>=up1[u]) { //更新最大的
            up2[u]=up1[u];
            up1[u]=up1[v]+w;
        } else if(up1[v]+w>=up2[u]) { //更新第二大的
            up2[u]=up1[v]+w;
        }
    }
}
void dfs_up(int u,int fa) { //往上寻找上面(子树外)的最大值
//用父节点的值更新子节点
    for(auto [v,w]:adj[u]) {
        if(v==fa) continue;
        if(w+up1[v]==up1[u]) { //最大的直线在一条树上
//用次长子树更新
            down[v]=max(up2[u]+w,down[u]+w);//取次长的子树+w与u最长的路径+w的最大值
        } else {
            down[v]=max(up1[u]+w,down[u]+w);
        }
        dfs_up(v,u);
    }
}
View Code
复制代码

待补充

 

A  Problem - 1324F - Codeforces

题意: 选择一些连通子树 让白子-黑子 的数量最大化

思路: 上下两遍扫描 处理出子树的最大值 然后再处理往上的最大值

复制代码
#include<bits/stdc++.h>
#define close     std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const ll MAXN = 3e5+7;
const ll mod =1e9+7;
const ll inf =0x3f3f3f3f;
const ll INF =0x3f3f3f3f3f3f3f3f;
//如果 cnt1>cnt2 就加上子树的贡献  
int dp[MAXN]; //dp[i]表示以i为根的树 可以存在的最大值
//转移方程 : 向下:如果子节点的>0 就加上
//              向上: 如果父节点的子节点>0 就取最大值 否则取加上和不加上的最大值 
vector<int> adj[MAXN];
void dfs_down(int u,int fa){
    for(auto v:adj[u]){
        if(v==fa)continue;
        dfs_down(v,u);
        if(dp[v]>0) dp[u]+=dp[v];
    }
}
void dfs_up(int u,int fa){
    for(auto v:adj[u]){
        if(v==fa) continue;
        if(dp[v]>0) dp[v]=max(dp[u],dp[v]);
        else dp[v]=max(dp[v],dp[u]+dp[v]);
        dfs_up(v,u);
    }
}
void solve(){
    int n;cin>>n;
    for(int i=1;i<=n;i++) {
        int a;cin>>a;
        if(a==0) dp[i]=-1;
        else dp[i]=1;
    }    
    for(int i=1;i<n;i++){
        int u,v;cin>>u>>v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    dfs_down(1,-1);
    dfs_up(1,-1);
    for(int i=1;i<=n;i++) cout<<dp[i]<<" ";
}
int main(){
    solve();
}
View Code
复制代码

树上Minmax - 问题 - ZSTUOJ

题意: 这题与A比较相似 但是是处理删除后的最大值并且使删除的操作数量更少 难度更大一点

思路:dp[i][1]为以i为根节点的子树中 删除最少的点能达到的最大值

            dp[i][0]为最小步数 

    只进行一次搜索就可以完成

    如果>0 肯定要加上 <0 肯定要切割

    如果等于0 已经操作次>1 就删掉 这样赚 否则加上

    最后的时候 因为建树的时候是以1为节点 如果以其他为节点的时候 还要多删一下 因此操作数+1

复制代码
#include<bits/stdc++.h>
#define close   std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const ll MAXN = 4e5+7;
const ll mod =1e9+7;
const ll inf =0x3f3f3f3f;
const ll INF =0x3f3f3f3f3f3f3f3f;
#define int long long
vector<int > adj[MAXN];
int dp[MAXN][2];//dp[i][0]表示 包含i的i子树 操作次数 dp[i][1]表示操作后的最大权值和 
int a[MAXN];
void dfs(int u,int fa){
    dp[u][0]=0,dp[u][1]=a[u];
    for(auto &v:adj[u]){
        if(v==fa) continue;
        dfs(v,u);
        if(dp[v][1]<0){
            dp[u][0]+=1;
        }
        else if(dp[v][1]>0){
            dp[u][0]+=dp[v][0];
            dp[u][1]+=dp[v][1];
        }
        else{//=0的情况 如果v的操作数>1 删掉 否则加上去 
            if(dp[v][0]>=1){
                dp[u][0]+=1;
            }
            else{
                dp[u][0]+=dp[v][0];
                dp[u][1]+=dp[v][1];
            }
        }
    }
//  if(adj[u].size()==1) 
}
void solve(){
    int n;cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<n;i++){
        int u,v;cin>>u>>v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    dfs(1,-1);
    int maxs=dp[1][1],maxt=dp[1][0];
    for(int i=2;i<=n;i++){
        if(dp[i][1]>maxs||(dp[i][1]==maxs&&dp[i][0]+1<maxt)){//更新最小操作数和最大权值和 
            maxs=dp[i][1];
            maxt=dp[i][0]+1;
        }
    }
//  for(int i=1;i<=n;i++){
//      cout<<"# "<<i<<" : "<<" t:"<<dp[i][0]<<" x:"<<dp[i][1]<<"\n";
//  }
//  if(maxs==dp[1][1]&&maxt==dp[1][0]){//如果不是在1这个点取到的 操作数+1 
        cout<<maxs<<" "<<maxt;
//  }
//  else cout<<maxs<<" "<<maxt+1;
}
signed main(){
    solve();
}
View Code
复制代码

篠塚真佑実的树 - 问题 - ZSTUOJ

题意:最多可使用一次传送门 算出最短距离

思路:赛场上是把所有点搞到一起 然后用lca算距离 一直re 赛后学长提醒才发现树有环了 寄

不过想想这道题还是比较简单的 两个点相通有两个方法 1使用传送门 2不使用传送门

使用的情况 算出每个点到传送点的最大距离 然后距离相加 (或者建虚点 dij也行

不使用的情况 直接d[u]+d[v]-d[lca(u,v)] 取最小即可

复制代码
#include<bits/stdc++.h>
#define close   std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const ll MAXN = 2e5+7;
const ll mod =1e9+7;
const ll inf =0x3f3f3f3f;
const ll INF =0x3f3f3f3f3f3f3f3f;
#define int long long
int vis[MAXN],f[MAXN];
vector<pair<int,ll> > adj[MAXN];
int par[MAXN][20],dep[MAXN];ll dis[MAXN],d[MAXN];
void dfs(int u,int fa){
    dep[u]=dep[fa]+1;
    par[u][0]=fa;
    for(int i=1;i<20;++i){
        par[u][i]=par[par[u][i-1]][i-1];
    }
    for(auto &i:adj[u]){
        int v=i.first;
        if(v==fa) continue;
        dfs(v,u);
    }
}
int getLCA(int u,int v){
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=19;i>=0;--i){
        if(dep[par[u][i]]>=dep[v]) u=par[u][i];
    }
    if(u==v) return u;
    for(int i=19;i>=0;i--){
        if(par[u][i]!=par[v][i]){
            u=par[u][i];
            v=par[v][i];
        }
    }
    return par[u][0];
}
void dfs_down(int u,int fa){
    for(auto &i:adj[u]){
        int v=i.first,w=i.second;
        if(v==fa) continue;
        dfs_down(v,u);
        //if(f[v]) dis[u]=0;
        dis[u]=min(dis[u],dis[v]+w);
    }
}
void dfs_up(int u,int fa){
    for(auto &i:adj[u]){
        int v=i.first,w=i.second;
        if(v==fa) continue;
    //  if(f[u]) dis[v]=0;
        dis[v]=min(dis[v],dis[u]+w); 
        dfs_up(v,u);
    }
}
void dfs_sum(int u,int fa){
    for(auto &i:adj[u]){
        int v=i.first,w=i.second;
        if(v==fa) continue;
        d[v]=d[u]+w; 
        dfs_sum(v,u);
    } 
}
void solve(){
    int m,n;cin>>n>>m;
    int root;
    memset(dis,0x3f3f3f3f,sizeof(dis));
    memset(d,0x3f3f3f3f,sizeof(d));
    for(int i=0;i<m;i++){
        int a;cin>>a;
        f[a]=1;
        if(i==0) root=a;  
        dis[a]=0;
    }
    for(int i=1;i<n;i++){
        int u,v,w;cin>>u>>v>>w;
        adj[u].push_back({v,w});
        adj[v].push_back({u,w});
    }
 
    d[root]=0;
    dfs_sum(root,-1);
    dfs(root,-1);
    dfs_down(root,-1);
    dfs_up(root,-1);
    int q;cin>>q;
    while(q--){
        int u,v;cin>>u>>v;
        int ans=d[u]+d[v]-2*d[getLCA(u,v)];
        ans=min(ans,dis[u]+dis[v]);
        cout<<ans<<"\n";
    }
}
signed main(){
    close;
    solve();
}
View Code
复制代码

Problem - 1363C - Codeforces

题意:每人轮流取一个点 谁先取到x谁输

思路:如果只有一个点 先手输 如果两个点 后手输 剩下判断奇偶性即可

复制代码
#include<bits/stdc++.h>
#define close     std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const ll MAXN = 3e5+7;
const ll mod =1e9+7;
const ll inf =0x3f3f3f3f;
const ll INF =0x3f3f3f3f3f3f3f3f;
vector<int> adj[MAXN]; 
int IN[MAXN];
int size[MAXN];
void dfs(int u,int fa){
    size[u]=1;
    for(auto v:adj[u]){
        if(v==fa) continue;
        size[u]+=size[v];
    }
}
void solve(){
    int n,x;cin>>n>>x;
    for(int i=1;i<=n;i++) size[i]=0,IN[i]=0;
    for(int i=1;i<n;i++){
        int u,v;cin>>u>>v;
        adj[u].push_back(v);
        adj[v].push_back(u);
        IN[u]++;
        IN[v]++;
    }
    if(IN[x]==1||IN[x]==0){
        cout<<"Ayush"<<"\n";
        return;
    }
//    dfs(1,-1);
    int maxs=0;

    if(n%2==0){
        cout<<"Ayush\n";
    
    }
        else cout<<"Ashish\n";
}
int main(){
    int t;cin>>t;
    while(t--)
    solve();
}
View Code
复制代码

Problem - 1404B - Codeforces

题意:A和B各在一个点上 分别一步可以走da与db A先走 如果A能追上B A胜 否则B

思路:首先如果B在A的一次控制范围 即d(a,b)<=da A必胜

           如果da*2>=树的直径 那么只要A站直径中间 B逃不走

   如果da*2>=db da可以慢慢逼近db //这里还不会证明 

      写起来简单

M-Monster Hunter_第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(南京) (nowcoder.com)

复制代码
#include<bits/stdc++.h>
#define close std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const ll MAXN =  3e3+7;
const ll mod = 1e9+7;
const ll inf = 0x3f3f3f3f;
#define int ll
int dp[MAXN][MAXN][2],a[MAXN];// i为顶点的子树 已经用了j个 此时用/不用  减少的最大贡献
vector<int> adj[MAXN];
int n;
int ans=0;
int sum[MAXN];
int sz[MAXN];
void dfs_sum(int u,int fa) {
    sum[u]=a[u];
    sz[u]=1;
    for(auto &v:adj[u]) {
        sum[u]+=a[v];
        dfs_sum(v,u);
    }
    ans+=sum[u];
}
void dfs(int u,int fa) {
    dp[u][1][1]=sum[u];
    for(auto &v:adj[u]) {
        dfs(v,u);
        for(int i=sz[u]; i>=0; i--) { //枚举父节点删了几个 
            for(int j=sz[v];j>=0;j--){
                if(j>0)
                dp[u][j+i][0]=max(dp[u][j+i][0],dp[u][i][0]+max(dp[v][j][0],dp[v][j][1]+a[v]));
                else 
                dp[u][j+i][0]=max(dp[u][j+i][0],dp[u][i][0]+dp[v][j][0]);
                if(j>0&&i>0)
                dp[u][j+i][1]=max(dp[u][j+i][1],dp[u][i][1]+max(dp[v][j][0],dp[v][j][1]));
                else if(i>0)
                dp[u][j+i][1]=max(dp[u][j+i][1],dp[u][i][1]+dp[v][j][0]);
            }
        }
        sz[u]+=sz[v]; 
    }

//    cout<<"delet : u "<<u<<"\n";
//    for(int i=0;i<=n;i++) cout<<"i = "<<i<<" "<<dp[u][i][0]<<" "<<dp[u][i][1]<<"\n";
}
void solve() {
    cin>>n;
    for(int i=1; i<=n; i++) {
        adj[i].clear();
        sum[i]=0;
        for(int j=0; j<=n; j++) dp[i][j][0]=dp[i][j][1]=0;
    }
    ans=0;
    for(int i=2; i<=n; i++) {
        int u;
        cin>>u;
        adj[u].push_back(i);
    }
    for(int i=1; i<=n; i++) cin>>a[i];
    dfs_sum(1,0);
    dfs(1,0);
    for(int i=0; i<=n; i++) {
        cout<<ans-max(dp[1][i][0],dp[1][i][1])<<" ";
    }
    cout<<"\n";
}
signed main() {
    int t;
    cin>>t;
    while(t--)
        solve();
}
View Code
复制代码

GProblem - H - Codeforces

复制代码
#include<bits/stdc++.h>
#define close std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long ll;
const ll MAXN =  4e5+7;
const ll mod = 1e9+7;
const ll inf = 0x3f3f3f3f;
#define int ll
int a[MAXN],b[MAXN];
vector<int> adj[MAXN];
int dp[MAXN],sum[MAXN];
void dfs(int u,int fa) {
    dp[u]=a[u];
    int tmp=0;
    int max_1=0;
    int max_2=0;
    for(auto v:adj[u]) {
        if(v==fa) continue;
        sum[u]+=a[v];
        dfs(v,u);
        tmp=max(tmp,a[v]);
        if(b[v]==3) {
            if(a[v]>a[max_1]) {
                max_2=max_1;
                max_1=v;
            } else if(a[v]>a[max_2]) {
                max_2=v;
            }
        }
        dp[u]+=dp[v];
        dp[u]-=a[v];
    }
    int temp=dp[u];
    for(auto &v:adj[u]){
        if(v==fa) continue;
        int k=a[v];
        for(auto &it:adj[v]){
            if(it==u) continue;
            k+=dp[it]-a[it];
        }
        if(v==max_1)
        dp[u]=max({temp+tmp,temp-dp[v]+a[v]+k+a[max_2],dp[u]});
        else dp[u]=max({temp+tmp,temp-dp[v]+a[v]+k+a[max_1],dp[u]});
    }
    dp[u]=max(dp[u],temp+tmp);
}
void solve() {
    int n;
    cin>>n;
    for(int i=1; i<=n; i++) cin>>a[i];
    for(int i=1; i<=n; i++) {
        cin>>b[i];
        if(b[i]==2) b[i]=1;
        adj[i].clear();
        dp[i]=0;
        sum[i]=0;
    }
    for(int i=1; i<n; i++) {
        int u,v;
        cin>>u>>v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    sum[0]=inf;
    dfs(1,0);
    cout<<dp[1]<<'\n';
}
signed main() {
    close;
    int t;
    cin>>t;
    while(t--)
        solve();
}
View Code
复制代码

 

posted @   xishuiw  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示