A. Elephant(Codeforces 617A)
思路
因为是求最小行走次数,因此可以贪心地尽可能多走
5
步。其中走
代码
#include <bits/stdc++.h>
using namespace std;
int x;
int main() {
scanf("%d", &x);
printf("%d\n", x / 5 + (x % 5 > 0));
return 0;
}
B. Chocolate(Codeforces 617B)
思路
想象一下,在每两个
需要注意的是序列的前缀
0
和尾缀
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 105;
int n, head;
ll cnt, ans, sum, a[maxn];
int main() {
cin >> n;
for(int i = 0; i < n; i++) {
cin >> a[i];
sum += a[i];
}
if(sum == 0) {
puts("0");
return 0;
}
// 找到序列中的第一个1
for(head = 0; head < n; head++) {
if(a[head] == 1) {
break;
}
}
ans = 1;
// 统计连续的0的个数同时累乘
for(int i = head; i < n; i++) {
if(a[i] == 1) {
ans *= (cnt + 1);
cnt = 0;
}
else {
cnt++;
}
}
cout << ans << endl;
return 0;
}
C. Watering Flowers(Codeforces 617C)
思路
显然圆心和与圆心不重合的点,这两点能且仅能刻画一个圆。设有
n
个点,那么对于圆心
这样,通过离散化思想我们就可以将问题转化为:在
O1
圆集合中选择一个元素,然后在
O2
圆集合中选择一个元素,使得这两个圆能将所有的点覆盖。
我们可以用
O(n2)
的复杂度来枚举两个圆,但是还要额外的复杂度来判断是否所有点在这两个圆内。这样总的复杂度是
O(n3)
。
考虑是否能让其中一个圆的集合有序,从而省略判断。于是我们将点集
P
按照点到
到此问题圆满解决了。但实际上还有复杂度更低的算法。我们可以将
r2(i)=max{O2k,k>i}
预处理出来,这样算上排序总的复杂度是
O(nlogn)
。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// 点结构体
struct Point {
ll x, y;
Point() {}
Point(ll x, ll y): x(x), y(y) {}
void input() {
scanf("%I64d%I64d", &x, &y);
}
ll sqrDis(const Point& o) const {
return (x - o.x) * (x - o.x) + (y - o.y) * (y - o.y);
}
};
const int maxn = 2010;
int n;
Point f1, f2, a[maxn];
ll ans, Max[maxn];
// 排序用的比较函数
bool cmp(Point a, Point b) {
return f1.sqrDis(a) < f1.sqrDis(b);
}
int main() {
scanf("%d", &n);
f1.input();
f2.input();
for(int i = 1; i <= n; i++) {
a[i].input();
}
sort(a + 1, a + n + 1, cmp);
Max[n+1] = 0;
// 预处理出同O2距离最远的点
for(int i = n; i >= 1; i--) {
Max[i] = max(f2.sqrDis(a[i]), Max[i+1]);
}
ans = Max[1];
for(int i = 1; i <= n; i++) {
ans = min(ans, f1.sqrDis(a[i]) + Max[i+1]);
}
printf("%I64d\n", ans);
return 0;
}
D. Polyline(Codeforces 617D)
思路
用笔在草稿纸上尝试后可以合理地提出假设,答案只有 1,2,3 三种。那么将输入根据三点的相对位置进行分类即可。
- 若三点有某个坐标分量两两相等,则可以用一个线段覆盖三个点。
- 若三点中有且仅有两个点 p1,p2 有某个坐标分量相等(不妨表示为 x1=x2 )。且另一个分量满足 yp3 不在 yp1 和 yp2 之间,则可以用两个线段覆盖三个点。
- 但以上两个条件都不满足的话,就只能用三个线段来覆盖三个点。
代码
#include <bits/stdc++.h>
using namespace std;
int x[5], y[5];
int solve() {
// 第一种情况
if(x[1] == x[2] && x[2] == x[3]) {
return 1;
}
if(y[1] == y[2] && y[2] == y[3]) {
return 1;
}
int a, b, c;
// 枚举上述p1,p2点,判断第二种情况
for(int i = 1; i <= 3; i++) {
for(int j = 1; j <= 3; j++) {
for(int k = 1; k <= 3; k++) {
if(i == j || j == k || i == k) {
continue;
}
if(x[i] == x[j]) {
a = min(y[i], y[j]);
b = max(y[i], y[j]);
c = y[k];
if(c <= a || c >= b) {
return 2;
}
}
if(y[i] == y[j]) {
a = min(x[i], x[j]);
b = max(x[i], x[j]);
c = x[k];
if(c <= a || c >= b) {
return 2;
}
}
}
}
}
// 否则是第三种情况
return 3;
}
int main() {
for(int i = 1; i <= 3; i++) {
cin >> x[i] >> y[i];
}
cout << solve() << endl;
return 0;
}
E. XOR and Favorite Number(Codeforces 617E)
思路
如果对区间和比较敏感的话,求区间的异或和可以先对原数组做预处理,然后就能做到常数时间查询。因而我们令
b[x]=a[1]⊕a[2]⊕...⊕a[x]
。于是区间
[x,y]
的异或和就能用
b[y]−b[x−1]
查询了。
即使做了优化,还是很难用合适的时间复杂度做查询。于是我们考虑通过小区间的答案推大区间的答案(或者相反)。假设我们已经知道
ans(x,y)
(区间
[x,y]
的查询结果)了。现在想求
ans(x,y+1)
,如果我们要想知道
y+1
位置的元素对答案的贡献的话,就必须要知道在区间
[x,y]
中有多少前缀和
b[y]⊕k
。为什么呢?因为
b[y]⊕a[x]=k
等价于
b[y]⊕k=a[x]
。于是我们可以用
sum[val]
来维护在当前区间,各个前缀异或和
val
出现的次数。从而
ans(x,y+1)=ans(x,y)+sum[b[y]⊕k]
。
建立了
ans(x,y)
到
ans(x,y+1)
的关系,就不难建立
ans(x,y)
到
ans(x,y−1)
,
ans(x−1,y)
和
ans(x+1,y)
的关系。从而满足了用莫队算法解决区间问题的条件。对查询二元组
(l,r)
按照分块思想排序后,这个问题就能用暴力移动双指针法在
O(nm−−√)
的复杂度内解决了。
另外要注意几个易错点:
- sum 数组的大小至少要开成数组中的数的2倍(因为取异或值后可能比原数大)。
- insert 和 erase 函数中两条语句的顺序不能变(因为统计的时候都是不计 idx 这个位置的元素的)。
- 所有的查询区间在输入后
l
要自减
1 (当然也可以在后面的操作中减)。 - insert(0) 这条语句不可删去(例如 k=1,a[1]=1 且第一个查询是 [1,1] 时,如果不加这句的话查询结果就是 0 <script type="math/tex" id="MathJax-Element-120">0</script> )。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10, maxa = 1e6 + 10;
int n, m, k, size;
// 排序用的查询结构体
struct query {
int l, r, idx;
query() {}
query(int l, int r, int idx): l(l), r(r), idx(idx) {}
int block() const {
return l / size;
}
bool operator < (const query& o) const {
return block() == o.block() ? r < o.r : block() < o.block();
}
};
// 莫队算法
struct M {
ll cnt;
int a[maxn], sum[maxa<<1];
ll ans[maxn];
query q[maxn];
// 输入数据
void input(int n) {
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for(int i = 2; i <= n; i++) {
a[i] ^= a[i-1];
}
}
// 向当前区间中加入新元素
void insert(int idx) {
cnt += sum[a[idx]^k];
sum[a[idx]]++;
}
// 想当前区间中擦除边界元素
void erase(int idx) {
sum[a[idx]]--;
cnt -= sum[a[idx]^k];
}
void solve(int m) {
int l, r;
for(int i = 1; i <= m; i++) {
scanf("%d%d", &l, &r);
q[i] = query(--l, r, i);
}
// 排序
sort(q + 1, q + m + 1);
insert(0);
// 暴力移动指针
int L = 0, R = 0;
for(int i = 1; i <= m; i++) {
l = q[i].l;
r = q[i].r;
for(; R < r; insert(++R));
for(; L > l; insert(--L));
for(; R > r; erase(R--));
for(; L < l; erase(L++));
ans[q[i].idx] = cnt;
}
}
// 输出答案
void output(int m) {
for(int i = 1; i <= m; i++) {
printf("%I64d\n", ans[i]);
}
}
}o;
int main() {
scanf("%d%d%d", &n, &m, &k);
size = sqrt(m);
o.input(n);
o.solve(m);
o.output(m);
return 0;
}