多校第一场 1003

来源:互联网 发布:软件作者权 编辑:程序博客网 时间:2024/05/18 00:52

炸弹

题目描述:

一个树,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 0
原创粉丝点击