url: https://www.luogu.com.cn/problem/P3621

tag:
动态规划,树形DP

思路:
tr[u][0/1] 来存二叉树。做两次dfs第一次求出最大的深度和最小的深度,如果相差大于1直接输出-1。第二次dfs从下到上递归,设计返回值为0/1/2分别表示都是浅层,都是深层和混合。当左边为浅层,右边为深层或者左边为混合,右边为深层时交换答案加1,如果存在某个节点两边都是混合的情况时说明无论怎么交换都不会改变当前的情况,则输出-1程序结束。最后如果存在一个方案使得可以交换成功就输出答案ans。

代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int tr[N][2],ans, mi = 0x3f3f3f3f, mx;
int n;
void dfs1(int u, int dep)
{
    if (u == -1)
    {
        mi = min(mi, dep);
        mx = max(mx, dep);
        return;
    }
    dfs1(tr[u][0], dep + 1), dfs1(tr[u][1], dep + 1);
}
int dfs2(int u, int dep)
{
    if (u == -1) return (dep != mi);
    int x = dfs2(tr[u][0], dep + 1);
    int y = dfs2(tr[u][1], dep + 1);
    ans += (!x && y) || (x == 2 && y == 1);
    if (x == 2 && y == 2)
    {
        cout << -1;
        exit(0);
    }
    if (x + y == 1 || x == 2 || y == 2) return 2;
    if (!(x + y)) return 0;
    return 1;
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> tr[i][0] >> tr[i][1];
    dfs1(1, 0);
    if (mx - mi > 1) cout << -1 << endl;
    else if (mi == mx) cout << 0 << endl;
    else
    {
        int _ = dfs2(1, 0);
        cout << ans << endl;
    }
    return 0;
}

url: https://www.luogu.com.cn/problem/P1273

tag:
动态规划,树形DP,背包DP

思路:
f[u][j] 表示第u个节点选择j个用户时的盈利数。使用分组背包的思想。状态转移方程为
f[u][k] = max(f[u][k], f[u][k - l] + f[j][l] - w[i]) 其中w表示如果将节点j接入时的成本。最后因为是求不亏本时最多可以观看转播的用户数,所以从m开始向下循环,直到找到第一个盈利大于等于0的然后输出用户数即可。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 3030;
int h[N], e[N * 2], ne[N * 2], w[N * 2], idx;
int n, m;
int p[N];
int s[N];
int f[N][N];
void dfs(int u, int fa)
{
    if (u > n - m) f[u][1] = p[u], s[u] = 1;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        dfs(j, u);
        s[u] += s[j];
        for (int k = m; k >= 0; k --)
        {
            for (int l = 1; l <= min(k, s[j]); l ++)
            {
                f[u][k] = max(f[u][k], f[u][k - l] + f[j][l] - w[i]);
            }
        }
    }
}
void add(int a, int b, int c)
{
    e[idx] = b;
    ne[idx] = h[a];
    w[idx] = c;
    h[a] = idx ++;
}
int main()
{
    memset(h, -1, sizeof h);
    memset(f, -0x3f, sizeof f);
    cin >> n >> m;
    for (int i = 1; i <= n - m; i ++)
    {
        int k;
        cin >> k;
        for (int j = 0; j < k; j ++)
        {
            int a, b;
            cin >> a >> b;
            add(i, a, b), add(a, i, b);
        }
    }
    for (int i = n - m + 1; i <= n; i ++) cin >> p[i];
    for (int i = 1; i <= n; i ++) f[i][0] = 0;
    dfs(1, 0);
    for (int i = m; i >= 0; i --)
    {
        if (f[1][i] >= 0)
        {
            cout << i;
            break;
        }
    }
    return 0;
}

url: https://www.luogu.com.cn/problem/P3205

tag:
动态规划,区间DP,2010

思路:
利用区间DP来做。设 f[i][j][0] 表示区间iji从左边进入区间的情况, f[i][j][1] 表示区间ijj从右边进入区间的情况。当i从左边进去时,上一个区间应该是 i + 1j ,所以只有当 h[i] < h[i + 1] 以及 h[i] < h[j] 时才能更新。同理当 h[j] > h[i] 以及 h[j] > h[j - 1] 时,j从右边进入区间,区间从 ij - 1 更新到 ij 。初始化为了避免当一个数字时有两种情况,所以可以是都假装是从左边进入区间,(或者是右边也行只要保证只保留一种情况就行)。最后答案将 f[1][n][0]f[1][n][1] 两种情况相加后取模输出即可。

代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1010, mod = 19650827;
typedef long long LL;
int n;
int h[N];
LL f[N][N][2];
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> h[i];
    for (int i = 1; i <= n; i ++) f[i][i][0] = 1;
    for (int len = 1; len <= n; len ++)
    {
        for (int i = 1, j = i + len; j <= n; i ++, j ++)
        {
            if (h[i] < h[i + 1]) f[i][j][0] += f[i + 1][j][0];
            if (h[i] < h[j]) f[i][j][0] += f[i + 1][j][1];
            if (h[j] > h[i]) f[i][j][1] += f[i][j - 1][0];
            if (h[j] > h[j - 1]) f[i][j][1] += f[i][j - 1][1];
            f[i][j][0] %= mod;
            f[i][j][1] %= mod;
        }
    }
    LL res = (f[1][n][0] + f[1][n][1]) % mod;
    cout << res << endl;
    return 0;
}

url: https://www.luogu.com.cn/problem/P2986

tag:
动态规划,树形DP,USACO,2010

思路:
可以参考 洛谷P3478 POI 2008 STA-Station 都是要求出当某一个节点为根时其他点到这个根的距离,我们可以用一次dfs来求出以任意选的一个节点作为根节点时的答案,比如选择节点1为根节点。然后再利用一次dfs来做节点转移的更新,求当其他节点做根节点时的答案,然后和全局答案做一个更新。最后输出这个答案即可。对于这道题,状态转移方程1是 f[u] += f[j] + w[i] * s[j];f[i] 表示以i为根节点时其他节点到该点的不方便值。状态转移方程2是 d[j] = d[u] - s[j] * w[i] + (cnt - s[j]) * w[i]; 先做初始化让 d[1] = f[1] 然后自上到下更新。

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
int e[N * 2], ne[N * 2], h[N], w[N * 2], idx;
int n;
LL cnt, ans;
int c[N];
LL f[N];
LL s[N];
LL d[N];
void add(int a, int b, int cc)
{
    e[idx] = b;
    w[idx] = cc;
    ne[idx] = h[a];
    h[a] = idx ++;
}
void dfs1(int u, int fa)
{
    s[u] = c[u];
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        dfs1(j, u);
        s[u] += s[j];
        f[u] += f[j] + w[i] * s[j];
    }
}
void dfs2(int u, int fa)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        d[j] = d[u] - s[j] * w[i] + (cnt - s[j]) * w[i];
        ans = min(ans, d[j]);
        dfs2(j, u);
    }
}
int main()
{
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++) cin >> c[i], cnt += c[i];
    for (int i = 0; i < n - 1; i ++)
    {
        int a, b, cc;
        cin >> a >> b >> cc;
        add(a, b, cc), add(b, a, cc);
    }
    dfs1(1, 0);
    d[1] = f[1];
    ans = d[1];
    dfs2(1, 0);
    cout << ans << endl;
    return 0;
}

url: https://www.luogu.com.cn/problem/P1040

tag:
动态规划,递归,枚举,区间DP,NOIP提高组,2003

思路:
使用区间dp的思路,令 f[i][j] 为节点i到节点j之间最大的加分,并用 root[i][j] 记录下这段区间的根节点。之后遍历每一种可能的区间,依据题目的公式更新数组f记录root最后得出结果输出 f[1][n] 即表示给定的二叉树tree的最高加分。然后再输出该情况时树的前序遍历即可。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 31;
int h[N], e[N * 2], ne[N * 2], idx;
int n;
LL f[N][N];
int root[N][N];
void print(int l ,int r)
{
    if (l > r) return;
    cout << root[l][r] << ' ';
    if (l == r) return;
    print(l, root[l][r] - 1),print(root[l][r] + 1, r);
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> f[i][i], root[i][i] = i;
    for (int len = 1; len <= n; len ++)
        for (int i = 1; i + len <= n; i ++)
        {
            int j = i + len;
            f[i][j] = f[i + 1][j] + f[i][i];
            root[i][j] = i;
            for (int k = i + 1; k <= j; k ++)
            {
                if (f[i][j] < f[i][k - 1] * f[k + 1][j] + f[k][k])
                {
                    f[i][j] = f[i][k - 1] * f[k + 1][j] + f[k][k];
                    root[i][j] = k;
                }
            }
        }
    cout << f[1][n] << endl;
    print(1, n);
    return 0;
}