D. Slime Escape(贪心/前缀和/模拟)

题目
参考

题意

给定n个数a[1],a[2],…a[n],a[i]可正可负。一开始你位于第k个数,且a[k]>=0。现在,你要从第k个点,到达第0个位置,或者第n+1个位置。你的初始血量是cur=a[k],每到达一个新的位置,你必须吸收它的能量值a[i](但第2次经过这个点就没有了,因为被你吸掉了),即经过新的点i,你的当前血量更新为cur=cur+a[i]。

问,能否在血量一直保持非负的情况下,走出边界,到达点0或点n+1。

思路

贪心思路很容易想到。

  • 向左扩展或向右扩展,看能否到达某个点,使得血量保持不减。
  • 向左扩展或向右扩展,看能否到达对应的终点位置(0或者n+1)。

简单来讲,就是如果能扩展当前区域,且保持血量不减甚至增加,那肯定扩展区域,提供自己的防御力。
如果能一次性莽到终点,只要血量非负,牺牲点血量又如何,都取得胜利了。

具体怎么实现,我们可以用前缀和,维护向左、向右区间的可扩展的区间段。
详见代码

代码

#include 
using namespace std;
#define ll long long
const int maxn = 200010;
#define pll pair<ll, ll>
void print_ve(const vector<pll> &v, string name) {
	cout << name << endl;
	for (auto &p: v) {
		printf("(%lld %lld) ", p.first, p.second);
	}
	cout << endl;
} 
ll a[maxn], pre[maxn];
ll cur;
int n, k;
void solve() {
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; ++i) {
    	scanf("%lld", &a[i]);
	}
	// assign a[k] to cur, and replace a[k] with 0
    cur = a[k];
    a[k] = 0;
    // init pre
    pre[0] = 0;
    for (int i = 1; i <= n; ++i) {
    	pre[i] = pre[i - 1] + a[i];
	}
	
    int l = k - 1, r = k;
    vector<pll> left, right;
    for (int i = l - 1; i >= 0; --i) {
    	// sum(a[i+1], a[i+2], ... , a[l]) >= 0 || i == 0
        if (pre[i] <= pre[l] || i == 0) {
            ll worst = 0, cur = 0;
            for (int j = l; j >= i + 1; --j) {
                cur += a[j];
                worst = min(worst, cur);
            }
            left.push_back({cur, -worst});
            l = i;
        }
    }
    for (int i = r + 1; i <= n; i++) {
    	// sum(a[r+1], a[r+2], ... ,a[i]) >= 0 || i == n
        if (pre[i] >= pre[r] || i == n) {
            ll worst = 0, cur = 0;
            for (int j = r + 1; j <= i; j++) {
                cur += a[j];
                worst = min(worst, cur);
            }
            right.push_back({cur, -worst});
            r = i;
        }
    }
//    print_ve(left, "left");
//    print_ve(right, "right");
    reverse(left.begin(), left.end());
    reverse(right.begin(), right.end());
    while (true) {
        bool flag = false;
        if (!left.empty() && cur >= left.back().second) {
            cur += left.back().first;
            left.pop_back();
            flag = true;
        }
        if (!right.empty() && cur >= right.back().second) {
            cur += right.back().first;
            right.pop_back();
            flag = true;
        }
        if (left.empty() || right.empty()) { // arrived at 0, n
            printf("YES\n");
            return;
        }
        if (!flag) { // can not expend any more.
            printf("NO\n");
            return;
        }
    }
}
int main() {
    int t;
    cin >> t;
    while (t--) solve();
}

你可能感兴趣的:(Codeforces,贪心算法,算法,c++)