2017暑期集训Day 14 树形dp

POJ 2486 Apple Tree

[Problem]

There are N nodes in the tree. Each node has an amount of apples. The people starts her happy trip at one node.She can eat up all the apples in the nodes she reachs. Now let you are
allowed to go more than K steps in the tree. Can you tell how many apples she can eat in at most K steps.

[Solution]

把起始点当做树根建树, 我们令f[i][j]代表在以i为根的子树中走j步可以获得的苹果,且最终回到i节点,令g[i][j]代表最后没有回到i节点,这样根节点的f值由所有儿子节点得到,每个儿子节点相当于一组背包,同一组只能选取一个或者不选;而g值保证儿子节点中只有一个g,其余全为f。

       for(int y)  // son
        for(int j = m; j >= 0; j--)
           for(int t = 0; t <= j; t++)  {
               if (t + 2 <= j)  f[x][j] = max(f[x][j], f[x][j - t - 2] + f[y][t]);  //   x -->  y--  > ...... --> y -- > x
               if (t + 2 <= j)  g[x][j] = max(g[x][j], g[x][j - t - 2] + f[y][t]);   // 表示之前的子节点没回到x,那么这一次必须回到x
               if (t + 1 <= j)  g[x][j] = max(g[x][j], f[x][j - t - 1] + g[y][t]);  //  选在这次不回到x
           }

[Ps]

关于分组背包
我们只需要把物品的那层循环与体积的循环颠倒位置即可。

//  0/1 背包
for item(1 --> n)
  for V (m -- > 0)
    {
    }
// 对于每个物品,一次选取,然后优化v的值,这样每次的v是由更小的v’更新而来,因此确保每件物品选取一次, 而等到更新下一件物品的时候,可以用上一件物品的最优解继续更新,这样确保了选取了
//多件物品
 分组背包:   
for V (m --> 0)
  for item( 1  --> n)
  {
  }

//而如果把v循环提到最外层,显然v是通过更小的v’更新得到的,在更新v时,此时v’保留的最优解是没有被任何一件物品更新的,因此确保了每组物品只选取一次

[Code]

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
#define mo 1000000007
const int N = 100 + 15;
const int M = 200 + 15;
const int inf = 0x3f3f3f3f;
int n, m, f[N][M], a[N], g[N][M];
vector<int> tree[N];
bool vis[N];
void dfs(int x)
{
    vis[x] = true;
    for(int i = 0; i < tree[x].size(); i++){
        int y = tree[x][i];
        if (vis[y])  continue;
        dfs(y);
        for(int j = m; j >= 0; j--)
           for(int t = 0; t <= j; t++)  {
               if (t + 2 <= j)  f[x][j] = max(f[x][j], f[x][j - t - 2] + f[y][t]);
               if (t + 2 <= j)  g[x][j] = max(g[x][j], g[x][j - t - 2] + f[y][t]);
               if (t + 1 <= j)  g[x][j] = max(g[x][j], f[x][j - t - 1] + g[y][t]);
           }
    }
    for(int i = 0; i <= m; i++){
        f[x][i] += a[x];
        g[x][i] += a[x];
    }
}
int main()
{
    while(~scanf("%d%d", &n, &m))
    {
        for(int i = 1; i <= n; i++){  tree[i].clear();  vis[i] = false;}
        for(int i = 1; i <= n; i++)
            for(int j = 0; j <= m; j++){
                f[i][j] = 0;
                g[i][j] = 0;
            }
        for(int i = 1; i <= n; i++)  scanf("%d", &a[i]);
        for(int i = 1; i < n; i++){
            int x, y;
            scanf("%d%d", &x, &y);
            tree[x].push_back(y);  tree[y].push_back(x);
        }
        dfs(1);
        int ans = 0;
        for(int i = 0; i <= m; i++)  ans = max(ans, max(f[1][i], g[1][i]));
        printf("%d\n", ans);
    }
    return 0;
}

POJ 3345 Bribing FIPA

[Problem]

给定一棵树,每个节点有个控制代价,控制此节点后,就可以控制这颗子树的所有节点,询问想要控制m个节点的最小代价

[Solution]

上一题的简化版, 对于f[x][i],代表以x为根的子树控制i个点的最小代价,这样对于x节点的每个子节点,相当于一组物品,每个子节点可以选取一个控制节点的数量,这就是这组物品选取的体积。

[Ps]

这道题目的读入很坑,确实学到了字符数组读入的一些东西

#include<sstream>
#include<cstring>
stringstream scin;
char buff[10000];
while(gets(buff))
{
  // 每次读入一整行字符串,读入到字符数组中去
  int n, m;
  sscanf(buff, "%d%d", &n, &m);
  //是不是很逆天,直接将这个字符数组的内容读入到int中
}
---------------------------------
gets(buff);
stringstream scin;
scin.clear();
scin.str(buff);
string name;
while(scin >> name)
{
   // 依次处理name,把空格作为分隔符
}

最后一点,关于g数组的初始化,或者说当需要多个数组记录状态的时候,都需要初始化。。
[Code]

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<sstream>
#include<map>
#include<string>
using namespace std;
#define mo 1000000007
const int N = 200 + 15;
const int M = 200 + 15;
const int inf = 0x3f3f3f3f;
int n, m, f[N][M], g[N], a[N], cnt, du[N];
char buff[250010];
vector<int> tree[N];
map <string, int > id;
int ID(string s)
{
    if (!id.count(s)) id[s] = ++cnt;
    return id[s];
}
void dfs(int x)
{
    if (!tree[x].size()){
        g[x] = 1; f[x][1] = a[x]; f[x][0] = 0;
        return;
    }
    f[x][0] = 0;
    g[x] = 1;
    for(int i = 0; i < tree[x].size(); i++){
        int y = tree[x][i];
        dfs(y);
        g[x] += g[y];
        for(int j = n ; j >= 1; j--){
            for(int t = 1; t <= min (j, g[y]); t++)
                if (f[x][j - t] < inf && f[y][t] < inf)
                    f[x][j] = min(f[x][j], f[x][j - t] + f[y][t]);
           // printf("f[%d][%d] = %d\n", x, j, f[x][j]);
        }
    }
    if (a[x] < inf)    f[x][g[x]] = a[x];
}
int main()
{
  //  freopen("b.in", "r", stdin);
    stringstream scin;
    while(gets(buff)) {
        if (buff[0] == '#')  break;
        sscanf(buff, "%d%d", &n, &m);
        cnt = 0;
        id.clear();
        for(int i = 0; i <= n; i++)  tree[i].clear();
        for(int i = 1; i <= n; i++)  du[i] = 0;
        for(int i = 1; i <= n; i++) {
            gets(buff);
            scin.clear();
            scin.str(buff);
            string name;
            scin >> name;
            int x = ID(name);
            scin >> a[x];
            while(scin >> name){
                int y = ID(name);
                du[y]++;
                tree[x].push_back(y);
            }
        }
        a[0] = inf;
        for(int i = 1; i <= n; i++)  if (!du[i])  tree[0].push_back(i);
        for(int i = 0; i <= n; i++)
            for(int j = 0; j <= n; j++)
              f[i][j] = inf;
        dfs(0);
        int ans = inf;
        for(int i = m; i <= n; i++)  ans = min(ans, f[0][i]);
        printf("%d\n", ans);
    }
    return 0;
}

POJ 2378 Tree Cutting

[Solution]

令f[x]代表i这个子树的节点数,x的子节点为y1,y2…yn,那么把x这个点去掉,便会产生一些联通分支,他们节点的个数分别为f[y1], f[y2],…..f[yn], n- f[x]
这需要这些值取个max与n/2判断大小即可

[Code]

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cmath>
#include<vector>
using namespace std;
const int N = 20000 + 500;
vector<int> s[N], tree[N];
bool vis[N];
int f[N], g[N];
int n;
void build(int x)
{
    vis[x] = true;
    for(int i = 0; i < s[x].size(); i++)
    {
        int y = s[x][i];
        if (vis[y])
            continue;
        tree[x].push_back(y);
        build(y);
    }
}
void dfs(int x)
{
    if (tree[x].size() == 0)
    {
        f[x] = 1;
        g[x] = 0;
    }
    f[x] = 1;
    g[x] = 0;
    for(int i = 0; i < tree[x].size(); i++)
    {
        int y = tree[x][i];
        dfs(y);
        f[x] += f[y];
        g[x] = max(f[y], g[x]);
    }
}
int main()
{
   // freopen("b.in", "r", stdin);
    scanf("%d", &n);
    for(int i = 1; i < n; i++)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        s[x].push_back(y);
        s[y].push_back(x);
    }
    build(1);
    dfs(1);
    for(int i = 1; i <= n; i++)
        g[i] = max(g[i], n - f[i]);
    bool res = false;
    for(int i = 1; i <= n; i++)
        if (g[i] <= n / 2)
           {
               printf("%d\n", i);
               res = true;
           }
    if (!res)
       printf("NONE");
    return 0;
}

POJ 3107 Godfather

[Solution]

方法基本和上一题相同

[Code]

#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;
const int N = 50000 + 500;
const int M = 100000 + 500;
int nxt[M], point[M];
int first[N], top = 0;
bool vis[N];
int f[N], g[N];
int n;
void add(int x, int y)
{
    top++;
    nxt[top] = first[x];
    first[x] = top;
    point[top] = y;
}
void dfs(int x)
{
    vis[x] = true;
    f[x] = 1;
    g[x] = 0;
    for(int k = first[x]; k; k = nxt[k])
    {
        int y = point[k];
        if (vis[y])
            continue;
        dfs(y);
        f[x] += f[y];
        g[x] = max(f[y], g[x]);
    }
}
int main()
{
   // freopen("b.in", "r", stdin);
    scanf("%d", &n);
    for(int i = 1; i < n; i++)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        add(x, y);
        add(y, x);
    }
    dfs(1);
    for(int i = 1; i <= n; i++)
        g[i] = max(g[i], n - f[i]);
    int mi = g[1];
    for(int i = 1; i <= n; i++)
        mi = min(mi, g[i]);
    int ans[N], res = 0;
    for(int i = 1; i <= n; i++)
        if (g[i] == mi)
        ans[++res] = i;
    for(int i = 1; i < res; i++)
        printf("%d ", ans[i]);
    printf("%d", ans[res]);
    return 0;
}

POJ 3140 Contestants Division

[Solution]

[Code]

#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 100000 + 500;
const int M = 200000 + 500;
int nxt[M], point[M];
int first[N], top = 0, Case = 0;
bool vis[N];
ll f[N], a[N];
ll sum;
int n, m;
void add(int x, int y)
{
    top++;
    nxt[top] = first[x];
    first[x] = top;
    point[top] = y;
}
ll abs(ll a)
{
    if (a < 0)
        return -a;
    return a;
}
void dfs(int x)
{
    vis[x] = true;
    f[x] = a[x];
    for(int k = first[x]; k; k = nxt[k])
    {
        int y = point[k];
        if (vis[y])
            continue;
        dfs(y);
        f[x] += f[y];
    }
}
int main()
{
   // freopen("b.in", "r", stdin);
    while(~scanf("%d%d", &n, &m) && n)
    {
        top = 0;
        for(int i = 1; i <= n ;i++)
        {
            first[i] = 0;
            vis[i] = false;
        }
        sum = 0LL;
        for(int i = 1; i <= n; i++)
        {
            scanf("%lld", &a[i]);
            sum += a[i];
        }
        for(int i = 1; i <= m; i++)
        {
            int x, y;
            scanf("%d%d", &x, &y);
            add(x, y);
            add(y, x);
        }
        dfs(1);
        ll ans = abs(f[1] * 2 - sum);
        for(int i = 1; i <= n; i++)
            ans = min(ans, abs(f[i] * 2 - sum));
        printf("Case %d: %lld\n", ++Case, ans);

    }
    return 0;
}

HDU 5326 Work

[Code]

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cmath>
#include<vector>
using namespace std;
const int N = 20000 + 500;
vector<int> s[N], tree[N];
bool vis[N];
int f[N];
int n, k;
void build(int x)
{
    vis[x] = true;
    for(int i = 0; i < s[x].size(); i++)
    {
        int y = s[x][i];
        if (vis[y])
            continue;
        tree[x].push_back(y);
        build(y);
    }
}
void dfs(int x)
{
    f[x] = 1;
    for(int i = 0; i < tree[x].size(); i++)
    {
        int y = tree[x][i];
        dfs(y);
        f[x] += f[y];
    }
}
int main()
{
  //  freopen("b.in", "r", stdin);
    while(~scanf("%d%d", &n, &k))
    {
        for(int i = 1; i <= n; i++)
            {
                s[i].clear();
                tree[i].clear();
            }
        for(int i = 1; i <= n; i++)
            vis[i] = false;
        for(int i = 1; i < n; i++)
        {
          int x, y;
          scanf("%d%d", &x, &y);
          s[x].push_back(y);
          s[y].push_back(x);
        }
        build(1);
        dfs(1);
        int ans = 0;
        for(int i = 1; i <= n; i++)
            if (f[i] == k + 1)
              ans++;
        printf("%d\n", ans);
    }


    return 0;
}

CodeForces 696B Puzzles

[Problem]

给定一棵树,从根节点开始遍历,并依次给节点编号,求每个节点的期望编号

[Solution]

很巧妙的方法。

当根节点x的期望编号为n1时,我们考虑其子节点y1、y2….yn的期望,由于进入每个节点都是等概率,那么y2在y1之前的概率有多大呢?0.5. y3在y1之前的概率有多大?还是0.5,这样我们令f[x]代表x的编号期望,g[x]保存的是x子树的节点个数,那么

f[y1] = f[x ] + 1 + 0.5 * g[y2] + 0.5 * g[y3] + ... +0.5 * f[yn]

[Code]

#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;
const int N = 100000 + 500;
const int M = 200000 + 500;
int nxt[M], point[M];
int first[N], top = 0;
bool vis[N];
int f[N];
double dp[N];
int n;
void add(int x, int y)
{
    top++;
    nxt[top] = first[x];
    first[x] = top;
    point[top] = y;
}
void dfs(int x)
{
    vis[x] = true;
    f[x] = 1;
    for(int k = first[x]; k; k = nxt[k])
    {
        int y = point[k];
        if (vis[y])
            continue;
        dfs(y);
        f[x] += f[y];
    }
}
void dfsdp(int x)
{
    vis[x] = true;
    for(int k = first[x]; k ; k = nxt[k])
    {
        int y = point[k];
        if (vis[y])
            continue;
        dp[y] = dp[x] + 1 + 0.5 * (f[x] - 1 - f[y]);
        dfsdp(y);
    }
}
int main()
{
   // freopen("b.in", "r", stdin);
    scanf("%d", &n);
    for(int i = 2; i <= n; i++)
    {
        int x;
        scanf("%d", &x);
        add(x, i);
    }
    dfs(1);
    for(int i = 1; i <= n; i++)
        vis[i] = false;
    dp[1] = 1;
    dfsdp(1);
    for(int i = 1; i < n; i++)
        printf("%.1f ", dp[i]);
    printf("%.1f", dp[n]);
    return 0;
}

LightOJ - 1382 The Queue

[Solution]

首先dp[i][j]代表有序的i个数和有序的j个数合并的方案数,显然
dp[i][j] = dp[i][j - 1] + dp[i - 1][j]
然后对于x节点的若干个子节点y1、y2….yn
直接合并即可

[Code]

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
#define mo 1000000007
const int N = 1000 + 100;
const int inf = 0x3f3f3f3f;
typedef long long ll;
ll f[N], dp[N][N], Case = 0, n;
int g[N], du[N];
vector<int> tree[N];
void init()
{
    for(int i = 1; i <= 1000; i++)
    {
        dp[0][i] = 1LL;
        dp[i][0] = 1LL;
    }
    for(int i = 1; i <= 1000; i++)
        for(int j = 1; j <= 1000; j++)
          dp[i][j] = (dp[i][j - 1] + dp[i - 1][j]) % mo;
}
void dfs(int x)
{
    if (!tree[x].size())
    {
        f[x] = 1;
        g[x] = 1;
        return;
    }
    int delta = 0;
    ll ans = 1LL;
    for(int i = 0; i < tree[x].size(); i++)
    {
        int y = tree[x][i];
        dfs(y);
        ans = (ans * f[y]) %mo;
        ans = (ans * dp[delta][g[y]]) % mo;
        delta += g[y];
    }
    g[x] = delta + 1;
    f[x] = ans;
}
int main()
{
 //   freopen("b.in", "r", stdin);
    int T;
    scanf("%d", &T);
    init();
    while(T--)
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++)
            {
                tree[i].clear();
                du[i] = 0;
            }
        for(int i = 1; i < n; i++)
        {
            int x, y;
            scanf("%d%d", &x, &y);
            tree[x].push_back(y);
            du[y] ++;
        }
        int res = -1;
        for(int i = 1; i <= n; i++)
            if (!du[i])
        {
            res = i;
            break;
        }
        dfs(res);
        printf("Case %d: %lld\n", ++Case, f[res]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值