【CQBZOJ - 3644】鱼肉炸弹

@鱼肉炸弹@


@题目描述@

舒克和贝塔终于下定决心要去营救被关押在众猫聚居的A城中的大米。
A城的构造是很奇怪的。A城中的所有N栋建筑沿着一条直线排列,没有两栋楼的高度相同,而大米就被关押在其中的某栋建筑中。每一栋建筑的顶上都有一些猫在看守。如果按照从一端到另一端的顺序将所有的建筑编号为1~N,那么第i栋建筑的高度为Hi,顶上开始时的猫的数量为Ci。
每一只猫不但可以看守住其所在建筑的楼顶,还可以看守住那些比它所在建筑要低的楼的楼顶,前提是没有被其他楼挡住。A城中的建筑是很高的,高到可以忽略他们之间的距离和它们的水平面积。于是可以认为,楼i上的猫能够看守楼j的楼顶,当且仅当楼i的高度不低于楼j,且楼i到楼j之间的所有楼房的高度都低于楼i。
现在神勇的贝塔已经潜入A城内部营救大米,而舒克则负责驾驶直升机提供空中支援。按照约定,贝塔找到并救出大米后会爬上楼顶释放信号让舒克前来接应。
舒克的飞机上装备了K枚鱼肉炸弹。每一枚鱼肉炸弹都可以在被投放到某一座楼的楼顶一段时间后使该楼所有猫失去行动能力。假设第i栋楼被Si只猫看守(注意Si只猫包括在该楼上的Ci只,已经在其他楼上的所有能看守该楼顶的猫),他希望使用这k枚鱼肉炸弹能够使的Si的最大值最小。请输出这个最小值。

输入
第一行有两个整数N和K。
第二行至第n+1行有两个整数,依次是编号为1的楼到编号为N的楼的高度(Hi)和楼顶的猫数(Ci).

输出
你只需要输出一个整数,表示使用K枚炸弹所能达到的Si中的最大值最小能是多少。

样例输入
3 2
1 2
3 1
2 2
样例输出
1

提示
数据规模:1<=N<=100000,1<=k<=5,1<=Hi<=10^9,0<=Ci<=10^9.

@分析@

【太神啦这道题!要我绝对想不到!】
考虑这样一件事实:最高的那栋楼将所有的楼分为两段区间,两部分的猫不会相互看到,即不会互相影响。这个过程相当于是将原问题拆成了两个互不相干的子问题。所以我们可以定义状态dp[i][j][k]表示区间[i, j]放k个炸弹的最优值,然后进行转移。但是……容易发现这样时间复杂度和空间复杂度是要上天的。
让我们再来看看另外一个东西:笛卡尔树。它以最大值a[i] = max为根,并以此将原序列分为两部分[1…i-1]与[i+1…n]作为它的左子树与右子树。……莫名感觉上面这个 dp 过程与笛卡尔树有些相似?是的。我们可以先以O(n)的时间复杂度构造出以高度为权值的笛卡尔树,然后在笛卡尔树上作树形dp,这样时间复杂度与空间复杂度都降到了O(n)。
具体来说,定义状态dp[i][j]表示以 i 为根,放置 j 个鱼肉炸弹的最优值,有如下两种转移:
dp[i][j]=min{max{dp[lch][k],dp[rch][jk]}+key[i]} d p [ i ] [ j ] = m i n { m a x { d p [ l c h ] [ k ] , d p [ r c h ] [ j − k ] } + k e y [ i ] } (结点i不放置炸弹)
dp[i][j]=min{max{dp[lch][k],dp[rch][jk1]}} d p [ i ] [ j ] = m i n { m a x { d p [ l c h ] [ k ] , d p [ r c h ] [ j − k − 1 ] } } (结点i放置炸弹)
最后 dp[root][k] 就是答案

@代码@

【其实这道题用区间dp的思想也可以做,用记忆化搜索避免无用状态即可】
【但是 笛卡尔树上作dp 听起来就很棒不是吗(๑•̀ㅂ•́)و✧】
若有什么问题可以参考代码或者留言在下面问我,我会尽力解答的~

#include<cstdio>
#include<stack>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 100000;
const ll INF = (1ll << 60);
struct node{
    node *ch[2];
    ll dp[6];
    int c, h;
}tree[MAXN + 5], *root, *NIL, *tcnt;
int H[MAXN + 5], C[MAXN + 5];
int N, K;
void Init() {
    root = NIL = tcnt = &tree[0];
    NIL->ch[0] = NIL->ch[1] = NIL;
}
node *NewNode(int id) {
    tcnt++;
    tcnt->ch[0] = tcnt->ch[1] = NIL;
    tcnt->c = C[id], tcnt->h = H[id];
    return tcnt;
}
node *BuildTree() {
    stack<node*>stk;
    for(int i=1;i<=N;i++) {
        node *nw = NewNode(i), *lst = NIL;
        while( !stk.empty() && stk.top()->h < nw->h ) {
            lst = stk.top();
            stk.pop();
        }
        nw->ch[0] = lst;
        if( !stk.empty() )
            stk.top()->ch[1] = nw;
        stk.push(nw);
    }
    node *ret = NIL;
    while( !stk.empty() ) {
        ret = stk.top();
        stk.pop();
    }
    return ret;
}
void dfs(node *rt) {
    for(int i=0;i<=5;i++)
        rt->dp[i] = INF;
    if( rt->ch[0] == NIL ) {
        if( rt->ch[1] == NIL ) {
            rt->dp[0] = rt->c;
            rt->dp[1] = 0;
        }
        else {
            dfs(rt->ch[1]);
            for(int i=0;i<=5;i++)
                rt->dp[i] = min(rt->dp[i], rt->ch[1]->dp[i] + rt->c);
            for(int i=0;i<=4;i++)
                rt->dp[i+1] = min(rt->dp[i+1], rt->ch[1]->dp[i]);
        }
    }
    else if( rt->ch[1] == NIL ) {
        dfs(rt->ch[0]);
        for(int i=0;i<=5;i++)
            rt->dp[i] = min(rt->dp[i], rt->ch[0]->dp[i] + rt->c);
        for(int i=0;i<=4;i++)
            rt->dp[i+1] = min(rt->dp[i+1], rt->ch[0]->dp[i]);
    }
    else {
        dfs(rt->ch[0]), dfs(rt->ch[1]);
        for(int i=0;i<=5;i++)
            for(int j=0;j<=i;j++)
                rt->dp[i] = min(rt->dp[i], max(rt->ch[0]->dp[i-j], rt->ch[1]->dp[j]) + rt->c);
        for(int i=1;i<=5;i++)
            for(int j=0;j<i;j++)
                rt->dp[i] = min(rt->dp[i], max(rt->ch[0]->dp[i-1-j], rt->ch[1]->dp[j]) );
    }
}
int main() {
    Init();
    scanf("%d%d", &N, &K);
    for(int i=1;i<=N;i++)
        scanf("%d%d", &H[i], &C[i]);
    root = BuildTree();
    if( K >= N ) {
        printf("0\n");
        return 0;
    }
    dfs(root);
    printf("%lld\n", root->dp[K]);
}

@END.@

就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值