您的位置:首页 > 其它

【解题报告】Codeforces Round #340 (Div. 2)

2016-11-30 22:58 483 查看
题目链接

A. Elephant(Codeforces 617A)

思路

因为是求最小行走次数,因此可以贪心地尽可能多走 5 步。其中走 5 步的次数可以通过 x5 求出来。而如果执行除法之后还有剩余的话( xmod5>0 )行走次数还要加 1 次。

代码

#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)

思路

想象一下,在每两个 1 之间插入“隔板”就能够完成任务。那么有多少种方法呢?设第 i 个 1 和第 i+1 个 1 之间有 z[i] 个 0 。那么在这两个 1 之间插入“隔板”的方法有 z[i]+1 种。根据计数原理的乘法法则(总方法数等于每个步骤的方法数之积), ans=∏(z[i]+1) 。

需要注意的是序列的前缀 0 和尾缀 0 的 z[i] ,是不能计入累乘的。

代码

#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 而言,可以通过这 n 个点构成 n 个圆,对于圆心 O2 而言也是如此。

这样,通过离散化思想我们就可以将问题转化为:在 O1 圆集合中选择一个元素,然后在 O2 圆集合中选择一个元素,使得这两个圆能将所有的点覆盖。

我们可以用 O(n2) 的复杂度来枚举两个圆,但是还要额外的复杂度来判断是否所有点在这两个圆内。这样总的复杂度是 O(n3) 。

考虑是否能让其中一个圆的集合有序,从而省略判断。于是我们将点集 P 按照点到 O1 的距离排序。这样当我们枚举到 pi 时,所有 pj(j<i) 都已经在圆 O1 中了。然后再从点 pk(k>i) 中找一个到 O2 有最大距离的点构成圆 O2 ,此时圆 O2 就包含了在圆 O1 之外的全部点。这样总的复杂度是 O(n2) 。

到此问题圆满解决了。但实际上还有复杂度更低的算法。我们可以将 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 )。

代码

#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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息