【笔记】【树形dp】

1>树的直径

【树形dp】

(图来自luogu)

 从图中可以得知,以任意一个点为根,树的直径必然有一个dep最小的点,

直径是这个点子树中,两个最长链的和,(如果只有一个链,另一个当做0就好)

这里可以用贪心证得

没有了后效性,就可以 叶子->根 ,dp转移状态了

#include<cstdio>
#include<cstdlib>
#include<vector>
using namespace std;
int n,ans;
const int N=10003;
vector <int> g[N];
int d1[N],d2[N];

void dfs(int rt,int f)
{
    int sz=g[rt].size() ;
    for(int i=0;i<sz;i++)
    {
        int nx=g[rt][i];
        if(nx==f) continue;
        
        dfs(nx,rt);
        if(d1[nx]+1>d1[rt])
        {
            d2[rt]=d1[rt];
            d1[rt]=d1[nx]+1;
        }
        else if(d1[nx]+1>d2[rt])
            d2[rt]=d1[nx]+1;
        ans=max(d1[rt]+d2[rt],ans);
    }
}

int main()
{
    int u,v;
    scanf("%d",&n);
    for(int i=1;i<n;i++)
        scanf("%d%d",&u,&v),g[u].push_back(v),g[v].push_back(u);
    
    dfs(1,0);
    printf("%d\n",ans);
    
    return 0;
}

【dfs x 2】

(来自luogu)

做两遍dfs,第一遍从任意一个点开始,找到离它最远的点。第二遍从第一遍找到的点开始,再找离他最远的点,这条路径就是树的直径。

证明: 

设A和B是直径的两个端点,C是第一次dfsdfs的起点,D是终点。若D不是A或B,则一定有一条更长的链。但这和上面说的“设A和B是直径的两个端点”是矛盾的。

#include<bits/stdc++.h>
using namespace std;
int n,ll,ss=1,ans;
vector<int>g[10005];//动态数组,不会的看底下的链接
bool f[100005];//记录该节点是否来过
inline void dfs(int l,int s,bool flag)//flag只是为了不写两个函数,因为两次dfs都是大同小异
{
    bool hh=false;//hh用来判断该节点能否向外延伸,如果不能就是搜到了终点
    for(register int i=0;i<g[s].size();i++)
     if(!f[g[s][i]])//如果没有来过
     {
        hh=true;//表示可以向外延伸
        f[g[s][i]]=true;//标记为走过
        dfs(l+1,g[s][i],flag);//继续dfs
     }
    if(!hh)//如果不能延伸
    {
        if(flag) ans=max(ans,l);//如果是要求直径长度,即第二次dfs
        else if(l>ll) ll=l,ss=s;//第一次要记录长度,用来判断,记录端点,给第二次dfs做起点
    }
    return ;
}
int main()
{
    scanf("%d",&n);
    for(register int i=1;i<n;i++)
    {
        int x,y;
        cin>>x>>y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    dfs(0,1,0);//第一次dfs
    memset(f,0,sizeof(f));//记得初始化!!!
    dfs(0,ss,1);//第二次dfs
    cout<<ans<<'\n';
    return 0;
}

习题:

数字转换

#include<cstdio>
#include<cstdlib>
#include<queue>
using namespace std;
int n;
const int N=5e4+3;
int sum[N];
vector <int> g[N];

void prepare()
{
    for(int i=1;i<n;i++)
        for(int j=i<<1;j<=n;j+=i)
            sum[j]+=i;
    for(int i=1;i<=n;i++)
        if(sum[i]<i)
            g[i].push_back(sum[i]),
            g[sum[i]].push_back(i);  
}

int ans;
int d1[N],d2[N];
void dfs(int rt,int f)
{
    int sz=g[rt].size() ;
    for(int i=0;i<sz;i++)
    {
        int nx=g[rt][i];
        if(nx==f) continue;
        dfs(nx,rt);
        
        if(d1[nx]+1>d1[rt])
            d2[rt]=d1[rt],d1[rt]=d1[nx]+1;
        else if(d1[nx]+1>d2[rt])
            d2[rt]=d1[nx]+1;
        ans=max(ans,d1[rt]+d2[rt]);
    }
}

int main()
{
    scanf("%d",&n);
    prepare();
    
    dfs(1,0);
    printf("%d\n",ans);
    
    return 0;
}

https://www.cnblogs.com/ymzjj/p/9767526.html

2>树上动态规划

例题:皇宫看守

一个点,只要父亲或者孩子节点被选即可,

求最小代价

仿照舞会设计状态,

找出有影响的状态,这里是分成三种:

(1)rt没选,fa选了,son不知道              《=       son的状态为2、3

(2)rt没选,fa不知道,son选了         《=     son的状态为3(一个为3,其他为2、3)

(3)rt选了,son可选可不选,fa可选可不选    《=     son的状态为1,、2、3

就像原来是2,3两个状态,现在如果选了fa,rt和son都不用选,所以多了一个状态

但是因为统计的时候会复杂,所以虽然状态中要求选fa,但是我们统计的时候,值是不包括fa的

#include<cstdio>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
int n;
const int N=1503,inf=1<<30;
int val[N],sz[N],f[N][3];
vector <int > g[N];

int root;
void dfs(int rt)
{
    f[rt][2]=val[rt];
    int mn=inf;
    
    for(int i=0;i<sz[rt];i++)
    {
        int nx=g[rt][i];
        dfs(nx);
        
        f[rt][0]+=min(f[nx][1],f[nx][2]);
        f[rt][1]+=min(f[nx][1],f[nx][2]);
        mn=min(mn,max(f[nx][2]-f[nx][1],0) );
        f[rt][2]+=min(f[nx][0],min(f[nx][1],f[nx][2]));
    }
    f[rt][1]+=mn;
}

int main()
{
    scanf("%d",&n);
    int u,v,w;
    for(int i=1;i<=n;i++)
    {    
        scanf("%d",&u);
        if(!root) root=u;
        scanf("%d%d",&val[u],&sz[u]);
        for(int j=0;j<sz[u];j++)
            scanf("%d",&v),g[u].push_back(v);  
    }
    
    dfs(root);
    printf("%d\n",min(f[root][1],f[root][2]));
    
    return 0;
}

3>树上背包dp

就跟背包一样做就好了

#include<cstdio>
#include<cstdlib>
#include<vector>
using namespace std;
int n,m;
const int N=303;
int d[N];
int f[N][N];
vector <int> g[N];

void dfs(int rt,int sum)
{
    if(!sum) return ;
    if(f[rt][sum]) return ;
    for(int i=1;i<=sum;i++)
        f[rt][i]+=d[rt];
    
    int p=sum-1;
    int sz=g[rt].size() ;
    for(int i=0;i<sz;i++)
    {
        int nx=g[rt][i];
        dfs(nx,p);
        
        for(int k=sum;k>1;k--)
            for(int j=1;j<k;j++)
                f[rt][k]=max(f[rt][k],f[rt][k-j]+f[nx][j]);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    int fa;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&fa,&d[i]);
        g[fa].push_back(i); 
    }
    
    dfs(0,m+1);
    printf("%d\n",f[0][m+1]);
    
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/xwww666666/p/11657063.html