UVALive - 5012 Rescue 【二分答案+数学公式】

版权声明:如需转载,记得标识出处 https://blog.csdn.net/godleaf/article/details/86692218

题目链接:https://vjudge.net/problem/UVALive-5012

因为题目的答案有单调递增的特点,所以很容易想到用二分答案来做,难得地方就是check函数要如何写,也就是说,如果给你一个p,要如何在规定的时间范围内判断这个p能否满足题目的要求(摧毁所有的石头)。

因为只能向左丢球,所以丢球的顺序肯定是从右向左丢的,比如说最右边的石头要想摧毁的话,你只能站在这个石头上向左丢球,如果一次没摧毁,就只能再丢一个球,直到摧毁这颗石头,然后再左一步,继续摧毁最右边的石头,只有这种方法。

因为p值会逐渐递减,递减的量和 i 与 j 的距离有关,能想到最暴力的方法就是从右向左丢球,每次把一个区间的石头减去对应的值,但是这种方法是会超时的。因为每一个石头都会受之前丢过的球的影响,会减去一些生命值,而这个值跟 i 有关,所以我们必须要想办法在不用知道 i 的情况下,也能求出我们想要的值。

我们假设 len为 第 j 个石头与第 i 个石头的距离。

当i == j 的时候,显然len == 0。这个时候p是没有衰减的,也就是伤害仍然是 p。

当 j == i+1的时候,这个时候 p会衰减 (len+1)^2,也就是伤害是 p - (len-1)^2。

假设 len == 1,那么当前的衰减值,对于下一个石头而言有 (len+1)^2 - len^2 = 2*len+1。

通过上一个石头的衰减值,我们能通过 2*len+1 这个公式,在不用知道 i 的情况下得到球到达当前石头的衰减值,而且这个公式是能够叠加的。

我们把上面的公式写成 2*len+kk,这个公式的叠加规律是,当只有一个球的时候,kk = 1, 每次向左一个石头,衰减值就是2*(len+1) + 1,当有两个球的时候,kk = 2,每次向左一个石头,衰减值就是 2*(len+2) + 2。

用 hit记录对石头的总伤害,每次向左遍历就减去衰减值。

因为每一个球都会有衰减到无的情况,所以我们还需要考虑当球衰减到无的时候,就丢弃这个球。

我在处理丢弃球的这部分问题的时候,卡了很久,思路挺乱的。

每次加入一个球的时候,就往队列里面存 i 这个值,当 p-(i-j)*(i-j) < 0的时候,就说明p到 j这个位置就衰减完了,这个时候的hit要么加上衰减多了的值,要么就减去上一次衰减完还剩下的值,这里代码里面选择的是后者。

在弹队列的时候不用担心会超时,咋一看弹队列好像是N^2的时间复杂度,实际上只有N而已。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <set>
#include <queue>

using namespace std;

typedef long long ll;

const int INF = 0x3f3f3f3f;
const int Maxn = 5e4+10;

ll a[Maxn], N, K;

bool ok(ll p) {
    queue<int> qu;
    ll kk = 0, dis = 0, k = K, tmp, hit = 0;
    for(int i = N-1; i >= 0; --i) {
        while(!qu.empty()) {
            tmp = qu.front();
            if((tmp-i)*(tmp-i) <= p) break;
            hit -= (p-(tmp-i-1)*(tmp-i-1));
            dis -= (tmp-i-1);
            kk--;
            qu.pop();
        }
        hit -= 2*dis+kk;
        dis += kk;
        while(hit <= a[i]) {
            if(k <= 0) return false;
            hit += p;
            k--;
            kk++;
            qu.push(i);
        }
    }
    return true;
}

int main(void)
{
    int T;
    scanf("%d", &T);
    while(T--) {
        scanf("%lld%lld", &N, &K);
        for(int i = 0; i < N; ++i) scanf("%d", &a[i]);
        ll R = 1e18, L = 1;
        
        while(L < R) {
            ll mid = (L+R)/2;
            if(ok(mid)) R = mid;
            else L = mid+1;
        }
        printf("%lld\n", R);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/godleaf/article/details/86692218
今日推荐