• 刷题Day 4-6 树形dp三题


    HDU2196

    树的直径:树上最远两点(叶子结点)的距离。

    方法一:暴力求解,从每个点开始遍历图,可以得到每个点v所在的最长路径max1和次长路径max2,注意的是最长路径和次长路径除了点v没有其他公共结点。如下图所示,经过点A的最长路径应该是下图左所示的路径,而非下图右所示的路径,通过A的最长路径必须是来自不同分支。方法一,暴力破解,每个点做一趟DFS的话,时间复杂度为(O(n^2))
    在这里插入图片描述

    方法二:从树上任意点u开始DFS(BFS)遍历图,得到距离u最远的结点v,然后从v点开始DFS遍历图,得到距离v最远的结点w, 则v、w之间的距离就是树的直径。
    证明:假设路径v-w为树的直径
    1)u位于v-w所在的路径上,如下图左图所示,那么从u点做DFS能访问到的最远点必然是v或w, 否则假设访问到的最远点为x, 如下图所示,有(,dist(u,x) geq dist(u,v), dist(u,x) geq dist(u,w))分两种情况讨论:
    a) 如果只取大于号, (dist(u,x)>dist(u,v) ,dist(u,x)>dist(u,w))
    那么(dist(u,x)+dist(u,v)=dist(v,x)>dist(u,v)+dist(u,w)=dist(v,w)), 那么v-w不是树的是直径,跟假设矛盾。
    b) 如果取大于等于号,(dist(u,x) geq dist(u,v) , dist(u,x)geq dist(u,w))
    假设(dist(u,x) = dist(u,v))那么(dist(x,w)=dist(v,w)), 这样也没问题,树的直径不唯一而已,那么x依然位于树的直径的一个端点上。
    2)u不位于v-w所在的路径上,如下图右图所示,那么有 (,dist(u,x)>dist(u,y,v),dist(u,x)>dist(u,y,w)),这里y是u到路径[v-w]的任意点,那么就有 (dist(u,x)+dist(u,y,w)=dist[x,w]>dist(v,y)+dist(y,w)=dist(v,w)), 那么说明那么v-w不是树的是直径,跟假设矛盾。
    综上,方法二正确,且复杂度为2趟DFS,因此复杂度为(O(n))。比方法一快很多。
    在这里插入图片描述

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    //maxv:源点能到的最远点,maxdis:最远点对应的距离, 
    const int maxn = 1e4 + 5;
    struct Edge { int to, next, w; }edges[2 * maxn];
    int head[maxn], maxdis,maxv, ne; 
    
    void add(int u, int v, int w) {
    	edges[ne] = { v, head[u], w };
    	head[u] = ne++;
    }
    
    //u:dfs的源点,f: u点的父节点,d2s:u点到源点的距离
    void dfs(int u, int f, int d2s) {
    	if (maxdis < d2s){
    		maxdis = d2s;
    		maxv = u;
    	}
    	for (int e = head[u]; e != -1; e = edges[e].next) {
    		int v = edges[e].to, w = edges[e].w;
    		if (v == f) continue;  //父节点已经访问过,防止重复遍历,相反孩子不会重复遍历。
    		dfs(v, u, d2s + w);
    	}
    }
    
    int main() {
    	int e, u, v, w, s;
    	cin >> e;
    	memset(head, -1, sizeof(head));
    	for (int i = 1; i <= e; i++) {
    		cin >> u >> v >> w;
    		add(u, v, w), add(v, u, w);
    	}
    	dfs(1, -1, 0); //从结点1开始遍历,找到最远点maxv及对应的最远距离maxdis
    	maxdis = 0;
    	dfs(maxv, -1, 0);//从结点maxv开始遍历,找到最远点对应的距离maxdis
    	cout << maxdis << endl;
    	return 0;
    }
    

    给一棵树,有n个结点,结点之间的边有权值,问每个结点的最远结点距离其多远 介绍一个定义:树的直径指的是树上两个最远点对。

    题解

    求法1:任取点u,找到离他最远的点v,然 后再找离v最远的点w,则((v,w))为直径。
    求法2:维护dp[u][0/1]为u子树内以u为端点 的最长路/次长路,答案就是 dp[u][0]+dp[u][1];

    我们还有一个定理,对于树上任意一个点 它的最远点对必定为直径两个点之间的一个。 故复杂度三次dfs

    P1077

    小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共(m)盆。通过调查顾客的喜好,小明列出了顾客最喜欢的(n)种花,从(1)(n)标号。为了在门口展出更多种花,规定第(i)种花不能超过(a_i)盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。
    试计算,一共有多少种不同的摆花方案。

    题解

    f[i][j]表示我取了前(i)种花,拿了(j)盆花的方案,则

    f[i][j]+=f[i-1][j-k](0<=k<=ai)
    
    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=105, mod = 1000007;
    int n, m, a[maxn], f[maxn];
    int main()
    {
        cin>>n>>m;
        for(int i=1; i<=n; i++) cin>>a[i];
        f[0] = 1;
        for(int i=1; i<=n; i++)
            for(int j=m; j>=0; j--) //注意,是01背包
                for(int k=1; k<=min(a[i], j); k++)
                  f[j] = (f[j] + f[j-k])%mod;
        cout<<f[m]<<endl;
        return 0;
    }
    

    未AC原因:

    可能这题是个背包。我错了orz

    P2014

    https://www.luogu.com.cn/problem/U53204

    现在有 (N) 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课 程b的先修课即只有学完了课程( ext A),才能学习课程( ext B))。一个学生要从这些课程里选择( ext M)门课程学习,问他能获得的最大学分是 多少?

    题解

    树形背包O(nm)的做法

    我们把原树叫做 A。

    定义  多叉树的后序遍历指的是:在搜索某个结点的过程中,先记录它的所有子树,再记录它本身。注意,如果有多个子树,则后序遍历的顺序不限。

    定义  多叉树的后序遍历序列指的是在上述过程中所记录下来的序列。

    我们不妨在 DFS 后把结点按照后序遍历序列重新编号。下图就是一个例子,左图为原树 A,右图为重新编号的树 B。

    现在,如果我们要复制一棵树 B(不妨称复制品为 C),将新树 B 里面的结点按编号(也就是按照 A 树的后序遍历序列)依次加入 C 中,我们会发现,每次加入的结点在当前情况下都是根结点。下图展示了放入 4, 7, 8, 9 号结点时,新图的情况。

    因此,设 (mathrm{dp}(i,j))表示将树 B 的结点 (1ldots i)放入新图,背包容量为 jj 时,所能取得的最大价值。设 (mathrm{size}_i)表示以 (i)为根的子树的大小。

    若取物品 (i),则可以取它的子树,则
    问题转化为「将结点 (1ldots i-1)加入 C,且背包容量为 (j-1)时,所能取到的最大价值」加上物品 (i) 的价值,
    所以答案为 (mathrm{dp}(i-1,j-1)+v_i)
    若不取物品 (i),则不可以取它的子树,则
    问题转化为「将『结点 (1ldots i-1)中不属于 (i)的子树的结点』加入 C,背包容量不变时,所能取到的最大价值」
    答案为 (mathrm{dp}(i-mathrm{size}_i,j))
    综上可得 (mathrm{dp}(i,j)=max(mathrm{dp}(i-1,j-w_i)+v_i,;mathrm{dp}(i-mathrm{size}_i,j)))
    易证其时间复杂度为 (O(NM))

    #include<iostream>
    #include<cstdio>
    #define maxn 1000
    using namespace std;
    int n,m,f[maxn][maxn],head[maxn],cnt;
    struct edge
    {
        int to,pre; 
    }e[maxn];
    inline int in()
    {
        char a=getchar();
        while(a<'0'||a>'9')
        {
            a=getchar();
        }
        int t=0;
        while(a>='0'&&a<='9')
        {
            t=(t<<1)+(t<<3)+a-'0';
            a=getchar();
        }
        return t;
    }
    void add(int from,int to)
    {
        e[++cnt].pre=head[from];
        e[cnt].to=to;
        head[from]=cnt;
    }
    void dp(int now)
    {
    //    f[now][0]=0;
        for(int i=head[now];i;i=e[i].pre)
        {
            int go=e[i].to;
            dp(go);
            for(int j=m+1;j>=1;j--)
            {
                for(int k=0;k<j;k++)
                {
                    f[now][j]=max(f[now][j],f[go][k]+f[now][j-k]);
                }
            }
        }
    }
    int main()
    {
        n=in(),m=in();
        for(int i=1;i<=n;i++)
        {
            int fa=in();
            f[i][1]=in();
            add(fa,i);
        }
        dp(0);
        printf("%d
    ",f[0][m+1]);
        return 0;
    }
    

    未AC原因:

    情况讨论错了。DFS序需要复习。

    要做就做南波万
  • 相关阅读:
    SwiftUI:看我展示52张扑克牌,“很快啊!”
    不会吧,这也行?iOS后台锁屏监听摇一摇
    分布式锁
    布隆过滤器原理
    redis缓存穿透
    布隆过滤器应用DEMO
    线程的声明周期
    分布式事务
    滑动窗口协议
    代理
  • 原文地址:https://www.cnblogs.com/liuziwen0224/p/st0504004-006.html
Copyright © 2020-2023  润新知