HDU 4302 Holedox Eating(线段树/优先队列/multiset)

1 篇文章 0 订阅
1 篇文章 0 订阅

题意:一条线段,在某些时刻上面的某些点会随机出现蛋糕。某种动物最初在0位置,在该动物想吃蛋时,它会跑到离他最近的蛋糕那里;如果有两个蛋糕与它的距离相同,就按照上次前进的方向走。输出它走过的总路程。
其实就是个水题,然而坑了一天,交了两页的记录 - - 写个博客纪念下
赛上用线段树区间最值过的,赛后看题解提到也可以线段树 + 二分做,又听说了 pq 和 multiset 的版本,就写了一发 pq,结果有个地方觉得写得不对但还是莫名AC了,觉得奇怪就坑了一下午 - - 还好最后终于找到了原因,并且用下面的数据叉掉了集训队里一个汉子的AC代码……这里output应该是26~
关于这个case和这个隐蔽修改方向导致WA的问题的解释以及multiset版本的代码,详见 lwt学长哒博客
总之,写代码前一定要思考清楚,要写出美观不易错的代码~

1  
20 13  
0 4  
1  
0 3  
1  
0 3  
1  
0 1  
0 5  
0 20  
1  
1  
1  
1  
  • 线段树区间最值
    网上思路较多,就不细说了 - -
//赛上的线段树区间最值版
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#define ALL(v) v.begin(), v.end()
#define CLR(array) memset(array, 0, sizeof(array))
#define DEBUG cout << "bug "
#define Fr first
#define FOR(i, n) for(int i = 0; i < n; i++)
#define MEM(array) memset(array, 0x3f, sizeof(array))
#define ls id << 1
#define lson id << 1, l, m
#define OFF(array) memset(array, -1, sizeof(array))
#define PR(x) cout << #x << " = " << x << "  "
#define PRLN(x) cout << #x << " = " << x << endl
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define root 1, 1, n
#define rs id << 1 | 1
#define rson id << 1 | 1, m + 1, r
#define Rep(i, a, b) for(int i = a; i >= b; i--)
#define repk(i, a, b) for(i = a; i <= b; i++)
#define Repk(i, a, b) for(i = a; i >= b; i--)
#define Sc second
#define SZ size()
using namespace std;

typedef long long ll;
typedef pair<int, int> P;
const int inf = 0x3f3f3f3f, maxn = 1e5 + 5;
struct Node {
    int lc, rc, cnt; //这里因为某点上可能有多个cake,所以还记录了区间内的cake数,其实单开数组记录每个点的cake数也可以
} tree[maxn << 2];
int lef, rig;

void build(int id, int l, int r) {
    tree[id].lc = inf;
    tree[id].cnt = 0;
    tree[id].rc = -inf;
    if(l == r) 
        return;
    int m = l + r >> 1;
    build(lson);
    build(rson);
}

void push_up(int id) {
    Node& now = tree[id];
    now.lc = min(tree[ls].lc, tree[rs].lc);
    now.rc = max(tree[ls].rc, tree[rs].rc);
}

void update(int id, int l, int r, int pos, int d) {
    Node& now = tree[id];
    now.cnt += d;
    if(l == r) {
        if(now.cnt) 
            now.lc = now.rc = l;
        else {
            now.lc = inf;
            now.rc = -inf;
        }
        return;
    }
    int m = l + r >> 1;
    if(pos <= m) update(lson, pos, d);
    else update(rson, pos, d);
    push_up(id);
}

void query(int id, int l, int r, int a, int b, int op) {
    if(!tree[id].cnt) return;
    if(a <= l && r <= b) {
        if(op) rig = min(rig, tree[id].lc);
        else lef = max(lef, tree[id].rc);
        return;
    }
    int m = l + r >> 1;
    if(a <= m) query(lson, a,  b, op);
    if(b > m) query(rson, a, b, op);
}


ll solve(void) {
    int n, q, a, b;
    ll ans = 0;
    int pre = 1, now = 1;
    scanf("%d%d", &n, &q);
    n++;
    build(root);
    while(q--) {
        scanf("%d", &a);
        if(a) {
            int goal;
            lef = -inf, rig = inf;
            query(root, 1, now, 0);
            query(root, now + 1, n, 1);
            if(now - lef != rig - now)
                goal = now - lef < rig - now ? lef : rig;
            else 
                goal = pre > 0 ? rig : lef;
            if(abs(goal) == inf) continue;

            update(root, goal, -1);
            ans += abs(goal - now);
            if(goal > now) pre = 1;
            else if(goal < now) pre = -1;
            now = goal;
        } else {
            scanf("%d", &b);
            update(root, b + 1, 1);
        }
    }
    return ans;
}

int main(void) {
    int t, kase = 0;
    scanf("%d", &t);
    while(t--) 
        printf("Case %d: %I64d\n", ++kase, solve());
    return 0;
}
  • 然后是二分版本哒~可以不记录区间最值,只要记录区间内cake数就可以了,但是要写两个query,不太好看。
    思路是每次查询到某个区间时,如果当前区间内没有cake,直接返回。如果要查找区间最右边的值,就优先查找右子树,然后才是左子树,查找区间左值类比即可。
    不过,网上虽然有人提了这个思路,但是几乎没有清晰代码,所以也贴下~
// 二分版本~
// 省略头文件50行
const int inf = 0x3f3f3f3f, maxn = 1e5 + 5;
int sum[maxn << 2];

void build(int id, int l, int r) {
    sum[id] = 0;
    if(l == r) return;
    int m = l + r >> 1;
    build(lson);
    build(rson);
}

void update(int id, int l, int r, int pos, int d) {
    sum[id] += d;
    if(l == r) return;
    int m = l + r >> 1;
    if(pos <= m) update(lson, pos, d);
    else update(rson, pos, d);
}

int queryl(int id, int l, int r, int a, int b) { // 查找区间最左边的cake的位置,没有则返回inf
    if(l == r) return l;
    int m = l + r >> 1;
    int res = inf;
    if(a <= m && sum[ls]) 
        res = queryl(lson, a, b);
    if(res == inf && b > m && sum[rs])
        res = queryl(rson, a, b);
    return res;
}
// 注意两个query的区别
int queryr(int id, int l, int r, int a, int b) { // 查找区间最右边的cake的位置,没有则返回 -inf
    if(l == r) return l;
    int m = l + r >> 1;
    int res = -inf;
    if(b > m && sum[rs])
        res =  queryr(rson, a, b);
    if(res == -inf && a <= m && sum[ls])
        res = queryr(lson, a, b);
    return res;
}

ll solve(void) {
    int n, q, a, b;
    ll ans = 0;
    int pre = 1, now = 1;
    scanf("%d%d", &n, &q);
    n++;
    build(root);
    while(q--) {
        scanf("%d", &a);
        if(!a) {
            scanf("%d", &b);
            update(root, b + 1, 1);
        } else {
            int goal, lef, rig;
            lef = queryr(root, 1, now);
            rig = queryl(root, now + 1, n);
            if(now - lef != rig - now)
                goal = now - lef < rig - now ? lef : rig;
            else 
                goal = pre > 0 ? rig : lef;
            if(abs(goal) == inf) continue;

            GOTO: update(root, goal, -1);
            ans += abs(goal - now);
            if(goal > now) pre = 1;
            else if(goal < now) pre = -1;
            now = goal;        
        }
    }
    return ans;
}

int main(void) {
    int t, kase = 0;
    scanf("%d", &t);
    while(t--) 
        printf("Case %d: %I64d\n", ++kase, solve());
    return 0;
}
  • pq版本也来一发,网上代码也很多,不细说了 - -
    不过说实话,用两个pq维护虽然听上去很机智,但是代码比较丑 也可能是我写的不好看吧 - - 感觉 multiset 或者 map 写会更简单
// pq 版~
const int inf = 0x3f3f3f3f, maxn = 1e5 + 5;

ll solve(void) {
    priority_queue <int> pql;
    priority_queue <int, vector<int>, greater<int> > pqr;
    int n, q, a, b;
    ll ans = 0;
    int pre = 1, now = 0;
    scanf("%d%d", &n, &q);
    while(q--) {
        scanf("%d", &a);
        if(!a) {
            scanf("%d", &b);
            if(b >= now) pqr.push(b);
            else pql.push(b);
        } else {
            int choice = 1, lef = -inf, rig = inf;
            if(pql.size())
                lef = pql.top();
            if(pqr.size())
                rig = pqr.top();
            if(now - lef != rig - now) {
                if(now - lef < rig - now) 
                    choice = -1;
            } else choice = pre;
            if(choice > 0 && rig != inf) {
                pqr.pop();
                ans += rig - now;
                if(rig != now)
                    pre = choice;                
                now = rig;
            } else if(choice < 0 && lef != -inf) {
                pql.pop();
                ans += now - lef;
                pre = choice;
                now = lef;
            }
        }
    }
    return ans;
}

int main(void) {
    int t, kase = 0;
    scanf("%d", &t);
    while(t--) 
        printf("Case %d: %I64d\n", ++kase, solve());
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值