题解:P14995 [Nordic OI 2020] Apple Delivery
题解:P14995 [Nordic OI 2020] Apple Delivery
诶期末考试咧,啷个还在刷竞赛题捏(HF 音)?
然后做了三天多没做出来,抢首 A 变成了首 WA…
我不行了我一直在找
前置知识
这是一道高斯圆问题(圆内整点问题)。
对于固定的半径
4 个象限的点数相同,4 个半轴上的点数也相同。所以一个圆内的点,可以以一象限和 y 轴正半轴为准,拆分成
枚举 x 坐标,勾股可得最大
由此得到
:::success[暴力做法(仅给暴力思路,看正解的可跳过)] 40pts
对于每个 r,均套用一遍公式得出每个圆内的点数和模 8 意义下的值,然后枚举或状压。
55pts
只需加上记忆化处理,可以去掉一部分 r 重合的情况。 :::
处理模 8 意义下的值
由上述公式可得,
继续拆分一象限,以
由于
删除半径
由上,我们得到了两个可重集合(分别对应模 8 为 1 和模 8 为 5 的半径)。要使送出的苹果数为 8 的倍数,就是在模 8 为 1 的集合中选
未分发的苹果数最少,则已分发的苹果最多。所以选出来的这些半径所包含的整点要尽量多,则半径要尽量大。故可以将半径升序排序,从后向前选。
任意考虑一个集合,如果在选完后,剩余的半径个数
于是枚举模 8 为 5 的集合中未选的项数,求出在这种情况下最多可选的能使
for(int k5 = 0; k5 < sz5; ++k5) {
int k1 = ((num5 - k5) * 5 + num1) % 8;
if(k1 >= sz1) continue;
ans = min(ans, sum5[k5] + sum1[k1]);
}
为简化时间复杂度,可以预处理前缀和,后续可以
for(int i = 1; i < sz1; ++i) {
int r = mod1[i - 1], poi = 0;
for(int j = 0; j < r; ++j)
poi += (int)floor(sqrt((double)(r * r - j * j)));
poi = poi * 4 + 1;
sum1[i] = sum1[i - 1] + poi;
}
for(int i = 1; i < sz5; ++i) {
int r = mod5[i - 1], poi = 0;
for(int j = 0; j < r; ++j)
poi += (int)floor(sqrt((double)(r * r - j * j)));
poi = poi * 4 + 1;
sum5[i] = sum5[i - 1] + poi;
}
总结一下做法
- 计算出每个半径在模 8 意义下的值,分别归入对应集合。
- 将两个集合按半径升序排序。
- 预处理出所需的前缀和。
- 枚举模 8 为 5 的集合中未选的项数,计算当前情况的最少未分发苹果数。
记得开 long long
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n, ans = 1e15;
vector<int> mod1, mod5;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i = 1, r; i <= n; ++i) {
cin >> r;
if(((int)floor((double)r / sqrt(2)) + r) % 2 == 0)
mod1.push_back(r);
else mod5.push_back(r);
}
sort(mod1.begin(), mod1.end());
sort(mod5.begin(), mod5.end());
int num1 = (int)mod1.size();
int num5 = (int)mod5.size();
int sz1 = min((int)8, num1 + 1);
int sz5 = min((int)8, num5 + 1);
vector<int> sum1(static_cast<size_t>(sz1), 0);
vector<int> sum5(static_cast<size_t>(sz5), 0);
for(int i = 1; i < sz1; ++i) {
int r = mod1[i - 1], poi = 0;
for(int j = 0; j < r; ++j)
poi += (int)floor(sqrt((double)(r * r - j * j)));
poi = poi * 4 + 1;
sum1[i] = sum1[i - 1] + poi;
}
for(int i = 1; i < sz5; ++i) {
int r = mod5[i - 1], poi = 0;
for(int j = 0; j < r; ++j)
poi += (int)floor(sqrt((double)(r * r - j * j)));
poi = poi * 4 + 1;
sum5[i] = sum5[i - 1] + poi;
}
for(int k5 = 0; k5 < sz5; ++k5) {
int k1 = ((num5 - k5) * 5 + num1) % 8;
if(k1 >= sz1) continue;
ans = min(ans, sum5[k5] + sum1[k1]);
}
cout << ans;
return 0;
}
呜呜呜审核大大给过吧...