AT_abc371_f [ABC371F] Takahashi in Narrow Road

赛后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;
}
AtCoder Beginner Contest 175 的 Problem C "Walking Takahashi" 中,目标是计算在给定初始位置 $ X $、移动次数 $ K $ 和每次移动距离 $ D $ 的情况下,最终距离原点的最小绝对值。这个问题的关键在于处理移动后的位置,使得最终结果尽可能接近原点。 ### 解题思路 1. **初始移动**:首先,将初始位置 $ X $ 取绝对值,因为无论方向,移动的距离是相同的。 2. **减少到原点附近**:计算最多可以向原点移动的步数 $ \text{straight} = \min(K, X / D) $。这一步将尽可能接近原点。 3. **剩余步数处理**:如果剩余步数 $ K $ 是偶数,则最终位置保持不变;如果是奇数,则最终位置会在 $ X $ 和 $ D - X $ 之间切换。 ### 代码实现 ```cpp #include <iostream> #include <algorithm> using namespace std; using ll = long long; int main() { ll X, K, D; cin >> X >> K >> D; X = abs(X); ll straight = min(K, X / D); K -= straight; X -= straight * D; if (K % 2 == 0) { cout << X << endl; } else { cout << D - X << endl; } return 0; } ``` ### 详细解释 1. **初始位置处理**:将 $ X $ 取绝对值,确保只考虑距离,不考虑方向。 2. **向原点移动**:通过计算 $ \text{straight} $,确定可以向原点移动的最大步数,并减少剩余步数 $ K $。 3. **剩余步数处理**:如果 $ K $ 为偶数,则最终位置不变;如果 $ K $ 为奇数,则位置会在 $ X $ 和 $ D - X $ 之间切换。 ### 复杂度分析 - **时间复杂度**:$ O(1) $,因为所有操作都是常数时间。 - **空间复杂度**:$ O(1) $,只需要常数空间。 ### 测试用例 1. **输入**:100 2 11 **输出**:78 2. **输入**:100 15 11 **输出**:1 3. **输入**:100 16 11 **输出**:10 这些测试用例验证了代码的正确性,并展示了不同情况下的处理方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值