多校第一场 1003

炸弹

题目描述:

一个树,n~1e5个点,每个点有一个权值,表示可以如果炸掉它,就可以免费炸掉距离他小于等于w[i]的点.每个点的权值<=100; 问最少炸几个可以全部炸完.

题解:

首先是一个点有一定范围的树上的dp.以前做过的是可以开二维dp[i][j],i是被j点控制的,j不限于i的子树. 但是这道题不能开二维,但是因为权值小于等于100,所以 用g[i][j]表示i为根的子树,从外面获取了炸到i的fa j的长度的权利. i点是1,i的儿子是2…. 这样j=0就是没有外界提供. 考虑转移g[i][j]:对于儿子v, 并不是单纯的g[v][j-1],而是对于v来说,从外界获取的小于等于j-1的g, 以及v可以向外面扩充的所有的j.(因为并不一定缺恰好i-1最好,我们先定义的都是恰好,可能反倒向外扩充一个东西更好). 所以我们要额外维护一个f[u][i],表示的是可以向外扩充i的距离的最小值.这样我们再根据实际重新定义下:f[u][i]指u点向外扩充大于等于i的最小值,g[u][i]表示u点从fa需要获取的小于等于i的长度的最小值,可能也往外放,(只要小于等于获取的值就行). 这样怎么转移? f[u][i]是横叉dp过去,强制当前的是要提供i的,其他的都用g[i]就行. g[u][i]每一个点都用g[v][i-1]. 然后重点来了,我们这样算出来的是恰好值,需要每一个点算完之后 都弄成实际意义的小于等于的值.

重点:

(1)树上一个点有控制范围.多开一维描述范围.
(2)g不够用,想明白恰好和满足限制下最小值的区别,额外增加f
(3)f和g的转移,之后再取最值.

代码:
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <ctype.h>
#include <limits.h>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <stack>
#include <set>
#include <bitset>
#define CLR(a) memset(a, 0, sizeof(a))
#define REP(i, a, b) for(int i = a;i < b;i++)
#define REP_D(i, a, b) for(int i = a;i <= b;i++)

typedef long long ll;

using namespace std;

const int maxn = 1e5 + 10;
const int maxDist = 100+10;
const int INF = 1e8+100;
int n, w[maxn];
vector<int> G[maxn];
int f[maxn][maxDist], g[maxn][maxDist];

void dfs(int u, int fa)
{
    REP(i, 0, G[u].size())
    {
        int v = G[u][i];
        if(v!=fa)
        {
            dfs(v, u);
        }
    }
    for(int i = 0;i<=100;i++)
    {
        f[u][i] = INF;
        int a, b;
        if(w[u]>=i)
        {
            b = 1;
        }
        else
        {
            b = INF;
        }
        a = 0;
        REP(j, 0, G[u].size())
        {
            int v = G[u][j];
            if(v!=fa)
            {
                int t = f[v][i+1];//简单的横叉转移
                //int t_min = min(m_g[v][i], m_f[v][i+1]);
                if(t!=INF)
                {
                    b = min(b+g[v][i] , a+t);
                }
                else
                {
                    b = b + g[v][i];
                }
                a += g[v][i];
            }
        }
        f[u][i] = b;

        g[u][i] = INF;//g的转移也很简单
        if(i>=1)
        {
            a = 0;
        }
        else
        {
            a = 1;
        }
        REP(j, 0, G[u].size())
        {
            int v = G[u][j];
            if(v!=fa)
            {
                int limit = max(0, i-1);
                int t = g[v][limit];
                //t = min(t,m_f[v][100]);
                a += t;
            }
        }
        g[u][i] = a;
    }

    f[u][101] = INF;
    g[u][101] = INF;
    for(int i = 100;i>=0;i--)//关键理解:我们定义的f和g有实际意义.
    {
        f[u][i] = min(f[u][i], f[u][i+1]);
    }
    g[u][0] = min(g[u][0], f[u][0]);
    for(int i = 1;i<=100;i++)
    {
        g[u][i] = min(g[u][i], g[u][i-1]);
    }

//    for(int i = 0;i<=3;i++)
//    {
//        printf("u is %d i is %d  f is %d  g is %d\n", u, i, f[u][i], g[u][i]);
//    }
}
void solve()
{
    dfs(1, 0);
    printf("%d\n", g[1][0]);
}

int main()
{
    freopen("3Cin.txt", "r", stdin);
    //freopen("3Cout.txt", "w", stdout);
    while(scanf("%d", &n) != EOF)
    {
        REP_D(i, 1, n)
        {
            scanf("%d", &w[i]);
        }
        REP_D(i,1,n)
        {
            G[i].clear();
        }
        REP_D(i, 1, n-1)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            G[a].push_back(b);
            G[b].push_back(a);
        }
        solve();
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值