• BZOJ 4557 JLOI2016 侦查守卫 树形dp


    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4557

    题意概述:

      给出一棵树,每个点付出代价w[i]可以控制距离和它不超过d的点,现在给出一些点,问控制这些点的最小代价是多少。

    分析:

      观察一下数据范围发现算算法的复杂度可能和d有关。横看竖看这像是一个树形dp,所以我们就把d搞到状态方程里面去嘛怎么就完全没有想到呢......

      既然要用树形dp,就要先分析一下性质。

      一个点如果被选择成为控制点,那么它可以控制的点有:子树中深度不超过d的点,祖先中和它距离不超过d的点,以及祖先的子树中的一些点。

      感觉很麻烦的样子......因为对于那些祖先子树中的点控制的方向突然向上又向下了。

      我们考虑到常用的技巧,在树形dp中,如果两个点会对答案产生贡献,我们在其LCA处统计贡献。于是我们设两个dp方程:

      f(i,x)表示i点的子树中需要被控制的点全部被控制,还可以向上控制x层的最小代价;g(i,x)表示i点的子树中x层及以下需要被控制的点全部被控制的最小代价。

      需要向上控制x层,那么儿子中就需要有点可以向上控制x +1层的点被选择,对于新来的子树j有两种情况,一个是我们需要的点在这个新的子树中,一个是我们需要的点在原来的子树中。

      f(i,x)=min(f(i,x)+g(j,x),f(j,x+1)+g(i,x+1))

      init:一开始把每点i当成孤点,那么向上控制1~d层就只有靠自己,f值初始化为w[i];f[i][0],g[i][0]根据这个点本身是否需要监视来判断。

      但是注意答案在控制的长度恰好为x的时候不一定是最优的,可能稍微控制的长度大一点答案反而更优,于是把方程的意义改一下,改成至少控制x层。

      g(i,x)=sum{g(j,x-1)|i->j},g(i,0)=f(i,0)

      小技巧:怎么维护至少这个性质?和看起来更劣的状态取min即可。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<cstdlib>
     5 #include<algorithm>
     6 #include<cmath>
     7 #include<queue>
     8 #include<set>
     9 #include<map>
    10 #include<vector>
    11 #include<cctype>
    12 #define inf 1e9
    13 using namespace std;
    14 const int maxn=500005;
    15 const int maxd=25;
    16 
    17 int N,D,M,W[maxn];
    18 struct edge{ int to,next; }E[maxn<<1];
    19 int first[maxn],np,f[maxn][maxd],g[maxn][maxd];
    20 bool ob[maxn];
    21 
    22 void _scanf(int &x)
    23 {
    24     x=0;
    25     char ch=getchar();
    26     while(ch<'0'||ch>'9') ch=getchar();
    27     while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    28 }
    29 void add_edge(int u,int v)
    30 {
    31     E[++np]=(edge){v,first[u]};
    32     first[u]=np;
    33 }
    34 void data_in()
    35 {
    36     _scanf(N);_scanf(D);
    37     for(int i=1;i<=N;i++) _scanf(W[i]);
    38     _scanf(M);
    39     int x,y;
    40     for(int i=1;i<=M;i++){
    41         _scanf(x); ob[x]=1;
    42     }
    43     for(int i=1;i<N;i++){
    44         _scanf(x);_scanf(y);
    45         add_edge(x,y); add_edge(y,x);
    46     }
    47 }
    48 void DFS(int i,int fa)
    49 {
    50     for(int d=1;d<=D;d++) f[i][d]=W[i];
    51     f[i][D+1]=inf;
    52     if(ob[i]) f[i][0]=g[i][0]=W[i];
    53     for(int p=first[i];p;p=E[p].next){
    54         int j=E[p].to;
    55         if(j==fa) continue;
    56         DFS(j,i);
    57         for(int d=0;d<=D;d++)
    58             f[i][d]=min(f[i][d]+g[j][d],f[j][d+1]+g[i][d+1]);
    59         for(int d=D;d>=0;d--) f[i][d]=min(f[i][d],f[i][d+1]);
    60         g[i][0]=f[i][0];
    61         for(int d=1;d<=D;d++) g[i][d]+=g[j][d-1];
    62         for(int d=1;d<=D;d++) g[i][d]=min(g[i][d],g[i][d-1]);
    63     }
    64 }
    65 void work()
    66 {
    67     DFS(1,0);
    68     printf("%d
    ",f[1][0]);
    69 }
    70 int main()
    71 {
    72     data_in();
    73     work();
    74     return 0;
    75 }
    View Code
  • 相关阅读:
    【就业】腾讯VS百度
    MySQL基础知识
    PHP读取远程文件并保存
    【GTK3.0】背景设置
    【GTK】信号量(signal)大全
    c# 调用win32 api
    PHP写窗体程序
    一个苏州IT人的5年挨踢经历面试篇(之二)
    【c++ Primer 】 4.10复习题 12题(int)、(int&)和(int*)
    线段树技巧
  • 原文地址:https://www.cnblogs.com/KKKorange/p/8678650.html
Copyright © 2020-2023  润新知