【解题报告】Codeforces Round #340 (Div. 2)
2016-11-30 22:58
483 查看
题目链接
需要注意的是序列的前缀 0 和尾缀 0 的 z[i] ,是不能计入累乘的。
这样,通过离散化思想我们就可以将问题转化为:在 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) 。
若三点有某个坐标分量两两相等,则可以用一个线段覆盖三个点。
若三点中有且仅有两个点 p1,p2 有某个坐标分量相等(不妨表示为 x1=x2 )。且另一个分量满足 yp3 不在 yp1 和 yp2 之间,则可以用两个线段覆盖三个点。
但以上两个条件都不满足的话,就只能用三个线段来覆盖三个点。
即使做了优化,还是很难用合适的时间复杂度做查询。于是我们考虑通过小区间的答案推大区间的答案(或者相反)。假设我们已经知道 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 )。
代码
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; }
相关文章推荐
- Codeforces Round #277.5 (Div. 2) 解题报告
- 解题报告:Codeforces Round #146 (Div. 1) B. Let's Play Osu! 概率DP
- Codeforces Round #327 (Div. 1) 解题报告
- Codeforces Round #326 (Div. 1) 解题报告
- Codeforces Round #290 (Div. 2) 解题报告 A.B.C.D.
- 解题报告:Codeforces Round #381 (Div. 1)B. Alyona and a tree
- codeforce 192 div2解题报告
- Topcoder SRM 585 DIV2 解题报告 //缺1000
- Codeforces #263 div2 解题报告
- codeforces_235_div2解题报告
- Codeforces Round #395 (Div. 2) 解题报告
- codeforces Round #269(div2) B解题报告
- 【解题报告】Codeforces Round #357 (Div. 2)
- codeforces Round #259(div2) C解题报告
- codeforces Round #258(div2) B解题报告
- CQUPT WEEKLY TRAINING (4)DIV2 解题报告
- 解题报告:Codeforces Round #193 (Div. 2) C. Students' Revenge 贪心
- codeforces #133 div2 解题报告
- Codeforces Round #283 (Div. 1)解题报告A.B.C.
- Codeforces Round #228 (Div. 1) 解题报告