赛后20分钟做出来了 F 题,第一次切 F,写篇题解纪念一下。
我们观察样例可以发现,每一步任务 ( t , g ) (t,g) (t,g) 之后,想要使答案最小,位置被 t t t 所影响的人一定是拼在一起的。为了验证这个想法,可以把其他的样例也都推一下,可以发现这是正确的。
那样我们就可以得出 O ( n q ) O(nq) O(nq) 的做法,找到影响的人(人数为 x x x),然后把他们按顺序拼到 [ g , g + x ] [g,g+x] [g,g+x] 里面,注意影响的人并不包括 t t t 本人。
但是我们并不满足于这样的做法,但是这种做法让人有一股优化到 log \log log 的冲动。
找到影响的人那部分,不就是二分可以解决的吗?(具体如何二分读者可以先思考一遍,我的代码当中会体现出来)
拼到区间里面,就相当于一个首项为 g g g,公差为 1 1 1 的等差数列,使我想到了 T233406 那道题,那道题是加法,而这道题只是变成了赋值,主题思路是不变的。
问题来了,如何计算步数?我们可以使用线段树当中的区间和查询,答案就是等差数列的各项和减去区间和即可。
于是我们得到了大体思路:
第一步:二分查找被影响到的人(容易发现是个区间)。
第二步:线段树维护等差数列赋值,还要支持区间查询。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define N 200010
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
#define mid ((l + r) >> 1)
int n, m, a[N];
int node[N << 2], lazy1[N << 2], lazy2[N << 2];//lazy1代表首项,lazy2代表公差
//node表示区间和
void pushup(int now) {//上推很显然就是加起来
node[now] = node[ls(now)] + node[rs(now)];
}
void pushdown(int now, int l, int r) {
if (lazy2[now] != 0) {//满足lazy1!=0或lazy2!=0条件均可
lazy2[ls(now)] = lazy2[now];
lazy2[rs(now)] = lazy2[now];//把公差赋值到左右儿子
lazy1[ls(now)] = lazy1[now];
lazy1[rs(now)] = lazy1[now] + (mid - l + 1) * lazy2[now];//维护首项
node[ls(now)] = (mid - l + 1) * lazy1[now] + (mid - l) * (mid - l + 1) / 2 * lazy2[now];
node[rs(now)] = (r - mid) * lazy1[now] + (r - mid - 1) * (r - mid) / 2 * lazy2[now] + (r - mid) *
(mid - l + 1) * lazy2[now];//计算公式,这里不详细阐述,读者应该自己推出来
lazy2[now] = 0;
lazy1[now] = 0;
}
}
void build(int now, int l, int r) {//建树
lazy1[now] = lazy2[now] = 0;
if (l == r) {
node[now] = a[l];
return;
}
build(ls(now), l, mid);
build(rs(now), mid + 1, r);
pushup(now);
}
int query(int now, int l, int r, int ql, int qr) {//查询区间和
if (ql <= l && r <= qr)
return node[now];
pushdown(now, l, r);
int res = 0;
if (ql <= mid)
res += query(ls(now), l, mid, ql, qr);
if (mid < qr)
res += query(rs(now), mid + 1, r, ql, qr);
return res;
}
void modify(int now, int l, int r, int ql, int qr, int x, int val) {//修改
if (ql <= l && r <= qr) {
node[now] = (l + r - 2 * ql) * (r - l + 1) / 2 * val + (r - l + 1) * x;
lazy2[now] = val;
lazy1[now] = (l - ql) * val + x;
return;
}
pushdown(now, l, r);
if (ql <= mid)
modify(ls(now), l, mid, ql, qr, x, val);
if (mid < qr)
modify(rs(now), mid + 1, r, ql, qr, x, val);
pushup(now);
}
int find_right(int x, int pos) {//找被影响的人的右端点
int l = x, r = n, ans;
while (l <= r) {
if (mid - x + pos > query(1, 1, n, mid, mid))//如果人堆到一个区间还是会造成影响
ans = mid, l = mid + 1;
else
r = mid - 1;
}
return ans;
}
int find_left(int x, int pos) {//找被影响的人的左端点
int l = 1, r = x, ans;
while (l <= r) {
if (pos - (x - mid) < query(1, 1, n, mid, mid))//同上
ans = mid, r = mid - 1;
else
l = mid + 1;
}
return ans;
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
build(1, 1, n);
cin >> m;
int ans = 0;
while (m--) {
int x, pos;
cin >> x >> pos;
if (query(1, 1, n, x, x) == pos)
continue;
else if (query(1, 1, n, x, x) < pos) {
int r = find_right(x, pos);
int len = r - x;
ans += (pos + pos + len) * (len + 1) / 2 - query(1, 1, n, x, r);
modify(1, 1, n, x, r, pos, 1);//上面的思路
} else {
int l = find_left(x, pos);
int len = x - l;
ans += query(1, 1, n, l, x) - (pos + pos - len) * (len + 1) / 2;
modify(1, 1, n, l, x, pos - len, 1);
}
}
cout << ans;
return 0;
}
574

被折叠的 条评论
为什么被折叠?



