【2022ICPC沈阳I题解】【值域线段树+贪心】The 2022 ICPC Asia Shenyang Regional Contest I. Quartz Collection

链接:I. Quartz Collection

补了一天,网上没找到容易看懂的题解,唯一一篇还是同学的CCSU_LRF,但他丫的又不是个学数据结构的,当初怎么写的不记得了,给我讲晕了。这里重新整理一下思路。

I. Quartz Collection(值域线段树+贪心)

题意

n n n 种石头,每种石头有两块价值分别为 a i , b i a_i, b_i ai,bi,只有当第一块被买走才能买第二块,小A先买一块,接下来以小B两块,小A两块的顺序轮流买(形如: A B B A B B A … ABBABBA\dots ABBABBA), 每种石头每个人只能买一次。
m m m 次更改,每次给出 t , x , y t, x, y t,x,y,即将第 t t t 种石头的两块价格分别改为 x , y x, y x,y
若两人都以最优策略进行购买,对于初始情况和 m m m 次修改后的情况询问小A买齐 n n n 种石头的最小花费。(每次更改都保留对后面询问都造成影响

思路

假设对于所有品种最开始小A,小B选择的都是第二块石头,此时的总花费为 s u m b = ∑ i n b i sumb = \sum_i^n{b_i} sumb=inbi ,考虑将哪些品种换成第一块石头。
对于 i i i 号石头,若换成第一块石头,花费将加上 a i − b i a_i - b_i aibi,贪心的想选择的石头 a i − b i a_i - b_i aibi 越小越好。

分情况讨论
a i − b i a_i - b_i aibi 从小到大排序

  1. a i − b i < = 0 a_i - b_i <= 0 aibi<=0
    越小越能节约钱 AB 都会尽可能的优先选更小的那么选择就形如 A B B A A B B A … ABBAABBA\dots ABBAABBA 于是A能选到的数就是排名为 i i i i m o d    4 = 0 , 3 i \mod 4 = 0,3 imod4=0,3

  2. a i − b i > 0 a_i - b_i > 0 aibi>0
    对于这里的数都会是尽可能的避免选择,而不是同样策略选小的。因为若是将第一块石头取走,那么对于另一方来说可以跟着将第二块石头取走,从而避免取第一块造成的亏损。
    (我们可以选情况1的二号石头 或者 情况2的二号石头来避免, 因为情况1的一号石头已经被取完了,二号石头本来就是必取的可以不视作亏损)

    某一方当前次数恰好用完的情况一定是 A B B / A / A B B A A ABB / A / ABBAA ABB/A/ABBAA 都是奇数, 而情况1 的一二号石头之和一定是偶数。
    所以对于第1种情况的石头不存在恰好被某一方恰好取完的情况,一定是某一方取完剩下的一块,还有一次机会被迫选第2种情况的排名第一第一块石头, 那么对方会跟着将此时的情况2排名第一的第二块石头取走再去取情况2的排名最靠前的一号石头, 之后轮流进行。

    于是局面变成AB轮流取石头 形如 A B A B … … ABAB…… ABAB…… 于是A能选到数就是排名为 i i i i m o d    4 = 0 , 2 或者 i m o d    4 = 1 , 3 i \mod 4 = 0,2 或者 i \mod 4 = 1,3 imod4=0,2或者imod4=1,3,具体要看情况1的最后一块石头是谁拿走的。

对于上述情况我们可以用值域线段树维护 a i − b i a_i - b_i aibi,对于排名取模可以记录在自己之前的数量即偏移量,而修改一种石头造成的改变我们可以看做直接修改偏移量。

最终答案就是: a n s = r e s l + r e s r + s u m b ans = resl + resr + sumb ans=resl+resr+sumb。其中
s u m b = ∑ i n b i sumb = \sum_i^n{b_i} sumb=inbi
r e s l resl resl a i − b i < = 0 a_i - b_i <= 0 aibi<=0 排名取模为 0 , 3 0,3 0,3的权值和。
r e s r resr resr a i − b i > 0 a_i - b_i > 0 aibi>0 排名取模为 0 , 2 0,2 0,2 1 , 3 1,3 1,3的权值和。

具体实现见代码

代码

#include 
using namespace std;

#define ls p << 1
#define rs p << 1 | 1
#define ll long long

const int N = 2e5 + 10, inf = 1e5;
int n, m;
struct node{
    int l, r, siz; // 区间内的数的数目, 也可以视为偏移量
    ll sum[4]; // sumi 区间内排名为 i % 4 的数权值之和
}tr[N * 4];

void build(int p, int l, int r){
    tr[p] = {l, r, 0};
    if(l == r) return ;
    int mid = (l + r) >> 1;
    build(ls, l, mid);
    build(rs, mid + 1, r);
}

void pushup(int p){
    tr[p].siz = tr[ls].siz + tr[rs].siz;
    for(int i = 0; i < 4; i ++) {
        tr[p].sum[i] = tr[ls].sum[i]; // 左区间的直接加上即可
    }
    int lsiz = tr[ls].siz;
    for(int i = 0; i < 4; i ++) {
        tr[p].sum[(lsiz + i) % 4] += tr[rs].sum[i]; // 需要加上左区间的数造成的偏移 
    }
}

void update(int p, int loc, int k) {
    if(tr[p].l == tr[p].r){
    	if(k == 1) tr[p].sum[tr[p].siz % 4] += loc;
    	else tr[p].sum[(tr[p].siz - 1) % 4] -= loc;
        tr[p].siz += k;
        // 错解: tr[p].sum[0] += loc * k; 相同的数也应当要排名
        return ;
    }
    int mid = (tr[p].l + tr[p].r) >> 1;
    if(loc <= mid) update(ls, loc, k);
    else update(rs, loc, k);
    pushup(p);
}

ll a[N], b[N], sumb;
ll query(int p = 1){
    // 对正负数分别查询即可
    ll resl = tr[ls].sum[0] + tr[ls].sum[3], resr = 0;
    if(tr[ls].siz * 2 % 4 == 0) resr = tr[rs].sum[0] + tr[rs].sum[2]; // 判断是谁取完的情况1 ,那么就是谁开始取情况2
    else resr = tr[rs].sum[1] + tr[rs].sum[3];
    return resl + resr + sumb;
}

int main(){
    cin >> n >> m;
    build(1, -inf, inf);
    for(int i = 1; i <= n; i ++){
        cin >> a[i] >> b[i];
        sumb += b[i];
        update(1, a[i] - b[i], 1);
    }
    cout << query() << "\n";

    for(int i = 1; i <= m; i ++){
        int t, ac, bc;
        cin >> t >> ac >> bc;
        update(1, a[t] - b[t], -1);
        update(1, ac - bc, 1);
        sumb = sumb - b[t] + bc;
        b[t] = bc;
        a[t] = ac;
        cout << query() << "\n";
    }
    return 0;
}

你可能感兴趣的:(数据结构,CPC,VP,算法,贪心算法)