树形dp day43

虽然已经很困了

但还是想起标题没取 就来写一下今天的鲜花

话说最近天气真热啊

好多时候都不想出去玩qwq

今天想到了一个很好玩的标题《由于找不到暑假工 所以只好来打休闲ACM》

休闲感觉称不上 但一定会感觉时间过的很快

好像没咋学 就放假了 没咋学 就开学了

AcWing 1073. 树的中心

树网的核 启发 直接找直接枚举每一个点即可 因为我们找直径时就维护了一个前缀和数组 直接枚举

O(n)

一些树的直径的理解:我们树是一个散出去的分支

我们只要知道他的最后一个尾巴(max 是谁那我们通过fa就可以求出直径了 那个尾巴就是我们不断维护的k

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
const int M = 1<<12;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int n,h[N],w[N<<1],ne[N<<1],e[N<<1],idx,fa[N<<1],dis[N<<1],k;
void add(int a,int b,int c){
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
void dfs(int f,int x){
    fa[x]=f;
    if(dis[x]>dis[k])k=x;
    for(int i=h[x];~i;i=ne[i]){
        int j=e[i];
        if(j==f)continue;
        dis[j]=dis[x]+w[i];
        dfs(x,j);
    }
}
signed main(){
    fast
    cin>>n;
    memset(h,-1,sizeof h);
    for(int i=1;i<n;i++){
        int a,b,c;cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    dis[1]=0,dfs(0,1);
    dis[k]=0,dfs(0,k);
    int ans=2e9;
    for(int i=k;i;i=fa[i])ans=min(ans,max(dis[k]-dis[i],dis[i]));
    cout<<ans<<endl;
    return ~~(0^_^0);
}

作者:Azusamitsusa
链接:https://www.acwing.com/solution/content/128394/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

然后这个代码我简化到只有29行发了一篇超短题解hh

#include <bits/stdc++.h>
const int N = 1e4+10;
int n,h[N],w[N<<1],ne[N<<1],e[N<<1],idx,fa[N<<1],dis[N<<1],k,ans=2e9;
void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int f,int x){
    fa[x]=f;
    if(dis[x]>dis[k])k=x;
    for(int i=h[x];~i;i=ne[i]){
        int j=e[i];
        if(j==f)continue;
        dis[j]=dis[x]+w[i];
        dfs(x,j);
    }
}
signed main(){
    std::cin>>n;
    memset(h,-1,sizeof h);
    for(int i=1;i<n;i++){
        int a,b,c;std::cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    dis[1]=0,dfs(0,1);
    dis[k]=0,dfs(0,k);
    for(int i=k;i;i=fa[i])ans=std::min(ans,std::max(dis[k]-dis[i],dis[i]));
    std::cout<<ans<<'\n';
    return ~~(0^_^0);
}

作者:Azusamitsusa
链接:https://www.acwing.com/solution/content/128394/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

AcWing 1072. 树的最长路径

想到了贪心 可是没看到有负权边

其实正解也是一个贪心 但是暴力遍历了所有点的那种情况

并且维护了一个max值和一个次max值

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
const int M = 1<<12;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int n,h[N],e[N<<1],ne[N<<1],w[N<<1],idx,ans=0;
void add(int a,int b,int c){
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
int dfs(int u,int fa){
    int d1,d2;
    d1=d2=0;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j==fa)continue;
        int t=dfs(j,u)+w[i];
        if(d1<t)d2=d1,d1=t;
        else if(d2<t)d2=t;
    }
    ans=max(ans,d1+d2);
    return d1;
}
signed main(){
    fast
    cin>>n;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n-1;i++){
        int a,b,c;cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    dfs(1,-1);
    cout<<ans<<endl;
    return ~~(0^_^0);
}

AcWing 1074. 二叉苹果树

我超 我居然没看出来这是一道模板题 呃呃

简直和有依赖的背包问题一模一样啊 就是吧权重从点换成了边

我们分组背包dp时 稍稍改一下就行了

有依赖的背包问题是保留父节点 从而要将父节点从j层删去相当于填满一个m-v【u】的背包

从而我们后面要更新f[u][i]

for(int i=m;i>=v[u];i--)f[u][i]=f[u][i-v[u]]+w[u];
    for(int i=0;i<v[u];i++)f[u][i]=0;

 而这个我们相当于保留一条连向父节点的边 而这个边是和s相连的

所以在k层必须保留一条边 而j层不用做改动

#include <bits/stdc++.h>
using namespace std;
const int N = 1e2+10;
const int M = 1<<12;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int f[N][N],h[N],e[N<<1],ne[N<<1],w[N<<1],idx,n,m,ans;
void add(int a,int b,int c){
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
void dfs(int u,int fa){
    for(int i=h[u];~i;i=ne[i]){
        int s=e[i];
        if(s==fa)continue;
        dfs(s,u);
        for(int j=m;j>=0;j--){
            for(int k=0;k<j;k++){
                f[u][j]=max(f[u][j],f[u][j-k-1]+f[s][k]+w[i]);
            }
        }
    }
}
signed main(){
    fast
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=1;i<n;i++){
        int a,b,c;cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    dfs(1,-1);
    cout<<f[1][m]<<endl;
    return ~~(0^_^0);
}

323. 战略游戏

又想贪心了 真蠢 还是没有证明就去写了

感觉自己脑子真的有毛病

后面写的时候又忘了初始化 初始化一定要在最开始 初始化一定要在最开始 初始化一定要在最开始

好在状态转移是对的 那没事了

#include <bits/stdc++.h>
using namespace std;
const int N = 1510;
const int M = 1<<12;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int n,h[N],e[N<<1],ne[N<<1],idx,f[N][N];//f[i][0/1]表示i节点没被选/被选的min
void add(int a,int b){
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
int dp(int u,int fa){
    f[u][0]=0,f[u][1]=1;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j==fa)continue;
        dp(j,u);
        f[u][0]+=f[j][1];
        f[u][1]+=min(f[j][1],f[j][0]);
    }
}
signed main(){
    while(cin>>n){
        memset(h,-1,sizeof h);
        idx=0;
        for(int i=1;i<=n;i++){
            int a,b;scanf("%lld:(%lld)",&a,&b);
            while(b--){
                int x;cin>>x;
                add(a,x),add(x,a);
            }
        }
        memset(f,0,sizeof f);
        dp(0,-1);
        cout<<min(f[0][0],f[0][1])<<endl;
    }
    return ~~(0^_^0);
}

1077. 皇宫看守

话说我第一时间居然没看出来这道题和上一道题的区别

后面感性理解了一下 发现区别还是很大

第一下卡在了他的状态转移会和上一层相交 感觉不是那么好做

其实我们还是应该把状态转移方程好好写下来看看再说

还有一个注意的点就是我们f[i][1]的状态转移是要将j的子树都除去的 而不是仅仅的wj

#include <bits/stdc++.h>
using namespace std;
const int N = 1510;
const int M = 1<<12;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int n,h[N],e[N<<1],ne[N<<1],idx,f[N][N],w[N];
bool st[N];
void add(int a,int b){
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
void dfs(int u){
    f[u][2]=w[u];
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        dfs(j);
        f[u][0]+=min(f[j][1],f[j][2]);
        f[u][2]+=min(min(f[j][1],f[j][0]),f[j][2]);
    }
    f[u][1]=inf;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        f[u][1]=min(f[u][1],f[j][2]+f[u][0]-min(f[j][1],f[j][2]));
    }
}
signed main(){
    cin>>n;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++){
        int a,c;cin>>a>>w[a]>>c;
        while(c--){
            int x;cin>>x;
            add(a,x);
            st[x]=true;
        }
    }
    int root=1;
    while(st[root])root++;
    dfs(root);
    cout<<min(f[root][1],f[root][2])<<endl;
    return ~~(0^_^0);
}

一些小理解

感觉状态表示如果想的差不多时 其实状态转移方程大概率都是可写的?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值