BZOJ1117 POI2009 救火站

提示:
1. 此贪心题

详细题解代码后:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <deque>
#include <stack>
#include <algorithm>

using namespace std;
typedef long long ll;

const int maxn = 1e5+1e3;
const int maxk = 25;
__inline int re() { int n; scanf("%d" , &n); return n; }

ll req[maxn][maxk];
ll has[maxn][maxk];

int res;
int n , s , k;
vector<int> g[maxn];

void range(int u , bool open = false)
{
    for(int i=0;i<=k;i++) for(int j=i;j>=0 && (open || j>=i-1);j--) 
    {
        ll cut = min(has[u][i] , req[u][j]);
        has[u][i] -= cut;
        req[u][j] -= cut;
        if(!has[u][i]) break;
    }
}

void dfs(int u , int fa)
{
    for(int i=0;i<g[u].size();i++) 
    {
        int v = g[u][i];
        if(v==fa) continue;
        dfs(v, u);
        for(int j=0;j< k;j++) req[u][j+1]+= req[v][j];
        for(int j=1;j<=k;j++) has[u][j-1]+= has[v][j];
    }

    req[u][0]++;

    ll urg = req[u][k] - has[u][k];
    if(urg>0)
    {
        int many = (urg+s-1)/s;
        has[u][k] += many*s;
        res+= many;
    }
    range(u);
}

int main(int argc, char *argv[]) {

    n = re(); s = re(); k = re();

    for(int i=1;i<n;i++)
    {
        int a = re() , b = re();
        g[a].push_back(b);
        g[b].push_back(a);
    }

    dfs(1, 0);
    range(1 , true);
    ll fin = 0;
    for(int i=0;i<=k;i++) fin+= req[1][i];
    res+= (fin+s-1)/s;

    printf("%d\n" , res);

    return 0;
}

每个结点保存到此结点距离为 0~k 的还可以覆盖和需要被覆盖的结点数。

如果当前结点距离为k 的可以覆盖的结点数少于需要被覆盖的结点数 , 说明这个结点不得不需要一些消防站。我们分配一些给它 , 最后我们把根结点需要建立的消防站计算出来就是答案啦。

每个结点两个量之间需要互相抵消 , 但并不是完全像最后计算根结点那样随意的抵消。 我们只用距离为i 的可以覆盖的结点数 去抵消 距离为i和i-1的需要被覆盖的结点数 , 详情见range.

为什么要这样做呢 , 如果我们去掉open 这个变量 , 那么抵消就是随意的 , 我们可能浪费了距离大的可以覆盖的结点配额。 我们只覆盖不得不覆盖的结点 , i 和 i-1 到这个结点父亲那里就已经不能被 i 覆盖了。

关于贪心的证明 , 有时候我们不能找到一种方法来证明这样做一定好 , 但是我们可以说明这样不比其他的决策更坏。 这个题就是如此 , 两个贪心的地方都可以这样说明 , 最后我们是正确的 , 只是因为我们的决策不太糟糕。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值