链接:I. Quartz Collection
补了一天,网上没找到容易看懂的题解,唯一一篇还是同学的CCSU_LRF,但他丫的又不是个学数据结构的,当初怎么写的不记得了,给我讲晕了。这里重新整理一下思路。
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 ai−bi,贪心的想选择的石头 a i − b i a_i - b_i ai−bi 越小越好。
分情况讨论
按 a i − b i a_i - b_i ai−bi 从小到大排序
a i − b i < = 0 a_i - b_i <= 0 ai−bi<=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。
a i − b i > 0 a_i - b_i > 0 ai−bi>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 ai−bi,对于排名取模可以记录在自己之前的数量即偏移量,而修改一种石头造成的改变我们可以看做直接修改偏移量。
最终答案就是: 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 ai−bi<=0 排名取模为 0 , 3 0,3 0,3的权值和。
r e s r resr resr 为 a i − b i > 0 a_i - b_i > 0 ai−bi>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;
}