HDU 3954 Level up

HDU 3954 Level up
链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=3954
题意:给你n个英雄,现在来了很多波怪物,每波怪物的会带来经验使英雄升级,英雄的得到的经验为其等级*e;询问某段区间的最大经验,英雄升级后经验值不会变为0,是直接累积的;

思路1,一颗线段树维护:
因为英雄的等级k不超过10,那么每个英雄升级的次数不会超过10次,因此我们对,每次来的e判断会不会使这段区间的英雄升级,如果有暴力单点更新,没有lazy标记下 return ;关键在与确定区间的最小升级系数;
如果升级那么有 : max_k * e + max_exp >= level[max_k+1],-> e >= (level[max_k+1] - max_exp)/max_k ;
令mn_exp = (level[max_k+1] - max_exp)/max_k。 只要e >= mn_exp 那么区间就会有升级,所以维护下区间的最小 mn_exp 即可,注意最高级后level设为 inf .

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 1e4 + 99;
int level[20], k;
struct segtree {
    int l, r;
    int max_exp, max_k;
    int exp_lazy;
    int rest;
}tr[maxn<<2];
void build(int l, int r, int pos)
{
    tr[pos].l = l; tr[pos].r = r;
    tr[pos].max_exp = 0; tr[pos].exp_lazy = 0;
    tr[pos].rest = level[2];
    tr[pos].max_k = 1;
    if (l == r)return;
    int mid = l + r >> 1;
    build(l, mid, pos << 1);
    build(mid + 1, r, pos << 1 | 1);
}
void push_up(int pos)
{
    tr[pos].rest = min(tr[pos << 1].rest, tr[pos << 1 | 1].rest);//维护区间的最小系数
    tr[pos].max_exp = max(tr[pos << 1].max_exp, tr[pos << 1 | 1].max_exp);
    tr[pos].max_k = max(tr[pos << 1].max_k, tr[pos << 1 | 1].max_k);
}
void push_down(int pos)
{
    if (tr[pos].exp_lazy == 0)return;
    tr[pos<<1].max_exp += tr[pos<<1].max_k*tr[pos].exp_lazy;
    tr[pos << 1|1].max_exp += tr[pos<<1|1].max_k*tr[pos].exp_lazy;//一定是叶子节点的k*父亲节点的lazy_exp;

    tr[pos << 1].exp_lazy += tr[pos].exp_lazy;
    tr[pos << 1 | 1].exp_lazy += tr[pos].exp_lazy;

    tr[pos << 1].rest -= tr[pos].exp_lazy;
    tr[pos << 1 | 1].rest -= tr[pos].exp_lazy;
    tr[pos].exp_lazy = 0;
}
int find_k(int exp)
{
    for (int i = k; i >= 2; --i)if (level[i] <= exp)return i;
    return 1;
}
void update(int ll, int rr, int e, int pos)
{
    if (ll == tr[pos].l&&tr[pos].r == rr)
    {
        if (tr[pos].rest > e) 
        {
            tr[pos].max_exp += tr[pos].max_k*e;
            tr[pos].rest -= e;
            tr[pos].exp_lazy += e;
            return;
        }
        if (ll == rr)
        {    
            tr[pos].max_exp += e*tr[pos].max_k;
            int rank = find_k(tr[pos].max_exp);
            tr[pos].max_k = rank;
            tr[pos].exp_lazy = 0;
            tr[pos].rest = (level[tr[pos].max_k + 1] - tr[pos].max_exp) / tr[pos].max_k;
            if (tr[pos].max_k == k) tr[pos].rest = 1 << 30;//这里一定要写,,,等级达到max后就不能升级,so,rest =  INF;
            return;
        }
    }

    push_down(pos);
    int mid = tr[pos].l + tr[pos].r >> 1;
    if (mid >= rr) update(ll, rr, e, pos << 1);
    else if (mid < ll)update(ll, rr, e, pos << 1 | 1);
    else
    {
        update(ll, mid, e, pos << 1);
        update(mid + 1, rr, e, pos << 1 | 1);
    }
    push_up(pos);
}
int ask_ans(int ll, int rr, int pos)
{
    if (ll == tr[pos].l&&tr[pos].r == rr)  return tr[pos].max_exp;
    push_down(pos);
    int mid = tr[pos].l + tr[pos].r >> 1;
    if (mid >= rr)  return ask_ans(ll, rr, pos << 1);
    else if (mid < ll) return ask_ans(ll, rr, pos << 1 | 1);
    else return max(ask_ans(ll, mid, pos << 1), ask_ans(mid + 1, rr, pos << 1 | 1));
}
int main()
{
    int t, ks = 1;
    scanf("%d", &t);
    while (t--)
    {
        memset(level, 0, sizeof level);
        printf("Case %d:\n", ks++);
        int n, qw;
        scanf("%d%d%d", &n, &k, &qw);
        build(1, n, 1);
        for (int i = 2; i <= k; ++i)scanf("%d", &level[i]);
        while (qw--)
        {
            char que;
            scanf(" %c ", &que);
            if (que == 'Q')
            {
                int l, r;
                scanf("%d%d", &l, &r);
                printf("%d\n", ask_ans(l, r, 1));
            }
            else
            {
                int l, r, e;
                scanf("%d%d%d", &l, &r, &e);
                update(l, r, e, 1);
            }
        }
        puts("");
    }
    return 0;
}

思路2 :
假如区间英雄的等级一样,那不就是个sb题吗。
由于 k <= 10, 我们可以对每个不同的等级建一颗线段树,没有英雄的位置值为 - inf,最初时,等级一的每个位置->0 。每次区间修改,对每颗线段数修改,再对每颗线段树维护最小升级的经验, 由于 k 相同,可以很简单的维护出来。如果区间有升级暴力更新,将现在对应得位置-> -inf ,再对应该升级到的那颗线段树暴力更新即可维护,注意,更新时,要倒着更新,至于为什么自己想想。查询时,对k可线段树取max即可。

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e4 + 1010;
using ll = long long;
#define inf 0x7f7f7f7f
int mx[11][maxn << 2], lazy[11][maxn << 2];
int lev[100], n, q;
inline void maintain(int rt, int k)
{
    mx[k][rt] = max(mx[k][rt << 1], mx[k][rt << 1 | 1]);
}
inline void push(int rt, int k)
{
    if (lazy[k][rt] == 0) return;
    mx[k][rt << 1] += lazy[k][rt];
    mx[k][rt << 1 | 1] += lazy[k][rt];
    lazy[k][rt << 1] += lazy[k][rt];
    lazy[k][rt << 1 | 1] += lazy[k][rt];
    lazy[k][rt] = 0;
}
void add(int l, int r, int rt, int k, int p, int v)
{
    if (l == r) mx[k][rt] = v;
    else
    {
        push(rt, k);
        int mid = l + r >> 1;
        if (p <= mid) add(l, mid, rt << 1, k, p, v);
        else add(mid + 1, r, rt << 1 | 1, k, p, v);
        maintain(rt, k);
    }
}
void update(int l, int r, int rt, int k, int ql, int qr, int e)
{
    if (mx[k][rt] < 0) return;
    if (ql <= l && qr >= r)
    {
        if (e*k < lev[k + 1] - mx[k][rt] && mx[k][rt] >= 0)
        {
            mx[k][rt] += e * k;
            lazy[k][rt] += e * k;
            return;
        }
        if (l == r && mx[k][rt] >= 0)
        {
            int rk = k;
            mx[k][rt] += k * e;
            while (lev[rk + 1] <= mx[k][rt]) rk++;
            add(1, n, 1, rk, l, mx[k][rt]);
            mx[k][rt] = -inf;
            return;
        }
        if (l == r)return;
    }
    push(rt, k);
    int mid = l + r >> 1;
    if (ql <= mid) update(l, mid, rt << 1, k, ql, qr, e);
    if (qr > mid) update(mid + 1, r, rt << 1 | 1, k, ql, qr, e);
    maintain(rt, k);
}
int query(int l, int r, int rt, int k, int ql, int qr)
{
    if (ql <= l && qr >= r) return mx[k][rt];
    else
    {
        push(rt, k);
        int mid = l + r >> 1, ret = 0;
        if (ql <= mid) ret = query(l, mid, rt << 1, k, ql, qr);
        if (qr > mid) ret = max(ret, query(mid + 1, r, rt << 1 | 1, k, ql, qr));
        return ret;
    }
}
int main()
{
    int k, t;
    scanf("%d", &t);
    while (t--)
    {
        int df;
        scanf("%d %d %d", &n, &k, &q);
        for (int i = 0;i < n * 4; ++i)
        {
            for (int j = 2;j <= k; ++j)
                mx[j][i] = -inf, lazy[j][i] = 0;
            mx[1][i] = lazy[1][i] = 0;
        }
        for (int i = 2;i <= k; ++i) scanf("%d", &lev[i]);
        lev[k + 1] = inf;
        static int ks = 0;
        printf("Case %d:\n", ++ks);
        while (q--)
        {
            char op[10];
            scanf("%s", op);
            int l, r, e;
            if (op[0] == 'Q')
            {
                scanf("%d%d", &l, &r);
                int ret = 0;
                for (int i = 1;i <= k; ++i)
                    ret = max(ret, query(1, n, 1, i, l, r));
                printf("%d\n", ret);
            }
            else
            {
                scanf("%d %d %d", &l, &r, &e);
                for (int i = k;i >= 1; --i)
                    update(1, n, 1, i, l, r, e);
            }
        }
        puts("");
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值