[ABC318F] Octopus 题解

发布时间 2023-12-19 12:20:00作者: Creeper_l

前言

赛时只做到了 E 题,赛后才来补的 F 题,还没做出来,看来还是我太菜了。看了题解过后感觉这道题的思路特别巧妙,于是就来写了这篇题解。

题意

简述一下题意。

\(n\) 个宝藏位置分别在 \(a_{i}\),另外有一只章鱼有 \(n\) 条触手,每条触手的长度为 \(b_{i}\)

求有多少个点 \(k\) 满足:章鱼站在点 \(k\) 的时候可以用触手抓到所有宝藏,且每个触手只能抓一个宝藏。

数据范围:\(n\le200,a_{i},b_{i}\le10^{18}\)

思路

因为宝藏位置的范围很大,所以不可能枚举所有位置,需要优化。我们可以把这些位置分成几个不同的区间,对于每个区间去计算答案,这样效率更高。

首先考虑如何判断 \(k\) 这一个点是否合法。首先把点 \(k\) 到每个宝藏的距离算出来,然后升序排一遍序。然后和 \(b\) 数组进行比较,如果有一位不合法(触手的长度不够)的话,那么点 \(k\) 就是不合法的;否则点 \(k\) 是合法的。时间复杂度 \(O(n \log n)\)

然后我们考虑如何计算区间中的答案。考虑一个区间 \(l\)\(r\) 满足 \(l\) 可以刚好够到一个宝藏,\(r\) 也可以刚好够到一个宝藏,但是 \(l+1\)\(r-1\) 中间没有一个点能刚好够到一个宝藏。那么只要 \(l+1\) 是合法的,那么 \(l+1\)\(r-1\) 中间的这些数也一定是合法的。

证明:因为 \(l\) 可以刚好够到一个宝藏,\(r\) 也可以刚好够到一个宝藏,\(l+1\)\(r-1\) 中没有一个点能够刚刚好够到宝藏。我们又知道从合法(触手长度大于等于离宝藏的距离)到不合法(触手长度小于离宝藏的距离)这个过程中一定会经过一个点能够刚刚好够到宝藏(触手长度等于离宝藏的距离),所以 \(l+1\)\(r-1\) 中一定没有不合法的点。对于 \(l\) 点和 \(r\) 点,单独判断一下是否合法,合法的话也加到答案中就行了。

这里 \(l\)\(r\) 的计算方式是 \(a_{i}-b_{j}\)\(a_{i}+b_{j}\),即每一个刚好可以够到一个宝藏的点,记得排序过后还要去重。这样的点的个数是 \(O(n^{2})\) 的,因为枚举了 \(i\)\(j\)。而 Check 的时间复杂度是 \(O(n \log n)\),所以总时间复杂度 \(O(n^{3} \log n)\),可以通过。

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define inf 0x3f
#define inf_db 127
#define ls id << 1
#define rs id << 1 | 1
#define register re
#define endl '\n'
typedef pair <int,int> pii;
const int MAXN = 2e3 + 10;
int n,a[MAXN],b[MAXN],p[MAXN * MAXN],cnt,tmp[MAXN],ans;
inline bool Check(int x)
{
	for(int i = 1;i <= n;i++) tmp[i] = abs(x - a[i]);
	sort(tmp + 1,tmp + n + 1);
	for(int i = 1;i <= n;i++) if(b[i] < tmp[i]) return false;
	return true;
}
signed main()
{
	cin >> n;
	for(int i = 1;i <= n;i++) cin >> a[i];
	for(int i = 1;i <= n;i++) cin >> b[i];
	for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) p[++cnt] = a[i] - b[j],p[++cnt] = a[i] + b[j];
	sort(p + 1,p + cnt + 1);
	cnt = unique(p + 1,p + cnt + 1) - p - 1; 
	for(int i = 1;i < cnt;i++) if(Check(p[i] + 1)) ans += (p[i + 1] - p[i] - 1);
	for(int i = 1;i <= cnt;i++) if(Check(p[i])) ans++;
	cout << ans;
	return 0; 
}