[题解][Codeforces]Good Bye 2019 简要题解
Posted xyz32768
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[题解][Codeforces]Good Bye 2019 简要题解相关的知识,希望对你有一定的参考价值。
构造题好评
,虽然这把崩了
A
题意
二人游戏,一个人有 (k_1) 张牌,另一个人 (k_2) 张,满足 (2le k_1+k_2=nle 100),每张牌上有一个数,保证所有的牌上的数互不相同且在 ([1,n]) 内
每回合双方都会出一张牌,牌上数小的一方的牌会给牌上数大的一方
拿到所有 (n) 张牌的一方赢得比赛
求两人都采取最优策略的情况下谁会赢
多组数据,数据组数 (tle100)
做法:贪心
设 (a) 和 (b) 为双方的最大数
显然如果 (a>b) 那么 A 必然每次都出最大的牌,这样可以必胜,反过来也是一样
于是判断 (a) 与 (b) 的大小关系即可,(O(tn))
代码
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
const int N = 105;
int n, m1, m2, a[N], b[N];
void work()
{
read(n); read(m1); read(m2);
for (int i = 1; i <= m1; i++) read(a[i]);
for (int i = 1; i <= m2; i++) read(b[i]);
std::sort(a + 1, a + m1 + 1); std::sort(b + 1, b + m2 + 1);
puts(a[m1] > b[m2] ? "YES" : "NO");
}
int main()
{
int T; read(T);
while (T--) work();
return 0;
}
B
题意
给定一个 (n) 个数的非负整数序列 (a),求 (a) 的一个子区间使得区间内数的 (max) 和 (min) 之差不小于区间长度,或者告知无解
多组数据,(sum nle2 imes10^5),(0le a_ile10^9)
做法:线段树+单调栈结论
先判断所有的长度为 (2) 的子区间
可以发现如果所有长度为 (2) 的子区间的极差都 (le 1) 则一定无解,因为显然 (n) 个数的,相邻数之差绝对值不超过 (1) 的序列极差的最大值只能是 (n-1)(序列单调时取得最大值)
(O(sum n))
代码
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
template <class T>
inline T Abs(const T &a) {return a < 0 ? -a : a;}
const int N = 2e5 + 5;
int n, a[N];
void work()
{
read(n);
for (int i = 1; i <= n; i++) read(a[i]);
for (int i = 1; i < n; i++) if (Abs(a[i] - a[i + 1]) >= 2)
return (void) printf("YES
%d %d
", i, i + 1);
puts("NO");
}
int main()
{
int T; read(T);
while (T--) work();
return 0;
}
C
题意
给定一个 (n) 个数的非负整数序列 (a)
给出一种方案,加入不超过 (3) 个 ([0,10^{18}]) 内的数,使得所有数的和是所有数异或和的 (2) 倍
可以证明一定存在解
多组数据,(sum nle10^5),(0le a_ile10^9)
做法:构造
做法有很多种,这里给出一个加入 (2) 个数的方案
设所有数的异或和为 (x),所有数的和为 (s),则加入的 (2) 个数分别为 (x) 和 (x+s)
这样所有数的异或和为 (xigoplus xigoplus (x+s)=x+s)
所有数的和为 (s+x+(x+s)=2(x+s))
条件得到满足。(O(sum n))
代码
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
typedef long long ll;
const int N = 1e5 + 5;
int n, a[N];
void work()
{
ll sum = 0; int xo = 0;
read(n);
for (int i = 1; i <= n; i++) read(a[i]), sum += a[i], xo ^= a[i];
puts("2");
printf("%d %lld
", xo, sum + xo);
}
int main()
{
int T; read(T);
while (T--) work();
return 0;
}
D
题意
交互题
有一个 (k) 以及一个隐藏的数 (m),还有一个 (n) 以及一个隐藏的 (n) 元序列,保证所有的数互不相同
每次可以询问 (k) 个下标,得出序列这些下标上的数中,第 (m) 小的数对应的下标
你需要用不超过 (n) 次操作询问出 (m)
(1le mle k<nle500),序列的元素在 ([0,10^9]) 范围内
做法:???(我也不知道叫什么)
认真分析之后发现 (n) 是没有用的,只有 (k<n) 是有用的,于是我们可以把 (n) 当成 (k+1)
这样不难发现合法的询问只有 (k+1) 种
接下去应该也有很多种做法,这里只说一种
先询问 (1dots k) ,设第 (m) 小的数对应下标为 (x) ,然后把 (1dots k) 中的 (x) 去掉换成 (k+1),可以得到 (x) 和 (k+1) 的大小关系(利用两次询问出的第 (m) 小的数值进行判断)
然后依次对于每个 (iin[1,k]-{x}),询问 ([1,k]-{i}+{k+1}),同样可以得出 (i) 和 (x) 的大小关系
这样就能得到 (m) 的值了,操作次数为 (k+1),复杂度 (O(k^2))
代码
#include <bits/stdc++.h>
const int N = 505;
int n, k, pos, val, ans = 1;
bool com;
int main()
{
int x, y;
scanf("%d%d", &n, &k);
printf("? ");
for (int i = 1; i <= k; i++) printf("%d ", i);
puts(""); fflush(stdout);
scanf("%d%d", &pos, &val);
printf("? ");
for (int i = 1; i <= k; i++) if (i != pos) printf("%d ", i);
printf("%d
", k + 1); fflush(stdout);
scanf("%d%d", &x, &y); com = y < val;
for (int i = 1; i <= k; i++) if (i != pos)
{
printf("? ");
for (int j = 1; j <= k; j++) if (j != i) printf("%d ", j);
printf("%d
", k + 1); fflush(stdout);
scanf("%d%d", &x, &y);
if ((x != pos) ^ com) ans++;
}
return printf("! %d
", ans), 0;
}
E
题意
平面上给定 (n) 个互不相同的点,坐标是绝对值不超过 (10^6) 的整数
求一种把 (n) 个点划分成两个非空集合的方案,使得不存在四个点(可以相同)(i,j,k,l) 满足 (i,j) 属于同一个集合,(k,l) 不属于同一个集合,且 (i) 到 (j) 的欧几里得距离与 (k) 到 (l) 的欧几里得距离相等
可以证明一定存在解
(2le nle10^3)
做法:构造
首先发现正三角形无解从而得出坐标为整数是有用的看到两个集合又容易想到奇偶性考虑能否有一种方案,对于两个点 (i,j) ,如果距离的平方为偶数则在同一个集合内,否则不在同一个集合内
这启发我们考虑模 (2) 意义下的加法。不难发现把两维坐标之和为奇数的放一个集合,偶数的放另一个集合可以满足条件
如果两维坐标之和的奇偶性全相同怎么办?
发现在这种情况下,把坐标系旋转 (45) 度并把坐标压缩到 (frac 1{sqrt 2}) 之后,所有的点坐标仍然是整数
于是这样处理之后继续做
每旋转一次之后任意两点之间的距离的平方都会除以 (2),所以复杂度 (O(nlog d)),(d) 为任意两点之间最小距离
代码
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
const int N = 1005;
int n, m, X[N], Y[N];
int main()
{
read(n);
for (int i = 1; i <= n; i++) read(X[i]), read(Y[i]);
while (1)
{
int c0 = 0, c1 = 0;
for (int i = 1; i <= n; i++)
(X[i] + Y[i] & 1 ? c1 : c0)++;
if (c0 && c1)
{
std::cout << c1 << std::endl;
for (int i = 1; i <= n; i++) if (X[i] + Y[i] & 1)
printf("%d ", i);
return puts(""), 0;
}
for (int i = 1; i <= n; i++)
{
int x = X[i] + Y[i], y = X[i] - Y[i];
X[i] = x - (c1 > 0) >> 1; Y[i] = y - (c1 > 0) >> 1;
}
}
return 0;
}
F
题意
给定一个 (01) 序列 (s)
求有多少个子区间满足其包含至少一个 (1),并且 (1) 的个数是区间长度的约数
(1le |s|le 2 imes10^5)
时限
8s
做法:根号分治
这题没开感觉十分亏第一部分:如何求 (1) 的个数不超过 (S) 的合法子区间数
先枚举右端点,再枚举 (1le ile S) 表示 (1) 的个数
显然右端点固定下来之后,满足区间内 (1) 的个数恰好为 (i) 的子区间的左端点是连续的一段
于是这时产生的区间内 (1) 的个数恰好为 (i) 的子区间的长度也是连续的一段,设其为 ([u,v])
那么要计入答案的就是 ([u,v]) 内 (i) 的倍数的个数,即 (lfloorfrac vi floor-lfloorfrac{u-1}i floor)
(O(nS))
第二部分:如何求 (frac{len}{cnt}) 不超过 (S) 的合法子区间数,(len) 为区间长度,(cnt) 为 (1) 的个数
还是枚举 (1lefrac{len}{cnt}=ile S)
考虑先给每个数一个权值 (a_x=1-[s_x=1] imes i)
易得一个子区间的 (frac{len}{cnt}=i) 当且仅当该区间的 (a) 之和为 (0)
于是求出 (a) 有多少对相同的前缀和即可
(O(nSlog S))(
sort
)或 (O(nS))(利用上哈希或std::unordered_map
)考虑把这两部分暴力结合起来
易得对于一个合法区间,其 (cnt) 和 (frac{len}{cnt}) 不可能同时超过 (sqrt n)
于是取 (S=lfloorsqrt n floor),跑一遍第二种暴力
然后再跑第一种暴力,注意直接算则会把 (cnt) 和 (frac{len}{cnt}) 都不超过 (S) 的子区间算重,需要限制长度不小于 ((i+1)S)
(O(nsqrt n))
代码
#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e5 + 5;
int n, S, a[N], top, stk[N];
ll ans, sum[N];
char s[N];
int main()
{
scanf("%s", s + 1); n = strlen(s + 1);
S = sqrt(n);
for (int d = 1; d <= S; d++)
{
for (int i = 1; i <= n; i++) a[i] = 1 - (s[i] == '1' ? d : 0);
sum[0] = 0;
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i];
std::sort(sum, sum + n + 1);
for (int i = 0; i <= n;)
{
int j = i;
while (j <= n && sum[i] == sum[j]) j++;
ans += 1ll * (j - i) * (j - i - 1) >> 1;
i = j;
}
}
for (int i = 1; i <= n; i++)
{
if (s[i] == '1') stk[++top] = i;
for (int j = 1; j <= S && j <= top; j++)
{
int l = i - stk[top - j + 1] + 1, r = i - stk[top - j];
if (l < j * (S + 1)) l = j * (S + 1);
if (l > r) continue;
ans += r / j - (l - 1) / j;
}
}
return std::cout << ans << std::endl, 0;
}
G
题意
给定 (n) 个整数 (a_{1dots n}),对于每个 (i) 都满足 (i-nle a_ile i-1)
求这 (n) 个整数中选出一个非空子集使得和为 (0)
(1le nle 10^6)
做法:构造
妙啊!!!( imes 1)
(不过听说随机乱搞能过???)首先 (i-nle a_ile i-1) 相当于 (1le i-a_ile n)
考虑建立图论模型:(i) 向 (i-a_i) 连一条边,显然这是一个基环树森林
考虑一个环,易得如果选出的子集为这个环上的点,环上每条边的贡献都是入点与出点的编号差,于是和为 (0)
于是随便找一个环即可,(O(n))
代码
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
const int N = 1e6 + 5;
int n, a[N], cnt;
bool vis[N];
void work()
{
int u;
read(n); cnt = 0;
for (int i = 1; i <= n; i++) read(a[i]), a[i] = i - a[i];
for (int i = 1; i <= n; i++) vis[i] = 0;
for (u = 1; !vis[u]; u = a[u]) vis[u] = 1;
for (int i = 1; i <= n; i++) vis[i] = 0;
for (; !vis[u]; u = a[u]) vis[u] = 1, cnt++;
for (int i = 1; i <= n; i++) vis[i] = 0;
printf("%d
", cnt);
for (; !vis[u]; u = a[u]) vis[u] = 1, printf("%d ", u);
puts("");
}
int main()
{
int T; read(T);
while (T--) work();
return 0;
}
H
题意
给定 (n) 个数 (a_{1dots n}),每个数都是不超过 (10^6) 的正整数
对于任意 (1le i<jle n),如果 (a_i<a_j) 则连边 ((i,j)),求连通块数
有 (q) 次单点修改,你需要在每次修改之后输出上面问题的答案,保证任何时候这 (n) 个数互不相同
(1le n,qle 5 imes10^5)
时限
8s
做法:线段树维护连续段
性质:每个连通块都是一段区间,且任意连通块内数的 (min) 都大于下一个连通块内数的 (max)
证明略
问题转化成有多少个 (1le ile n) 满足对于任意 (jle i,k>i) 都有 (a_j>a_k)
即整个序列前 (i) 大的数恰好占据了序列前 (i) 个位置
考虑暴力,把所有的数从大到小插入
易得组成恰好一个前缀连通块的条件为不存在 (1le i<n) 使得 (i) 位置还没被插入,(i+1) 位置已经被插入
于是考虑开一棵值域为 ([1,10^6]) 的线段树,位置 (h) 储存插入了所有 (ge h) 的数之后满足如上条件的 (i) 个数,并维护区间最小值及最小值个数
对于所有 (a_i<a_{i+1}),我们让线段树的区间 ([a_i+1,a_{i+1}]) 加一
这样答案就是全体最小值的个数(如果最小值不为 (0) 则答案为 (0))
修改时讨论 (pos-1,pos,pos+1) 三个位置的影响并重新进行区间加即可
但上面的过程中忽略了一个条件,如果 (h) 不与任意的 (a_i) 相等,那么这样的 (h) 是不能计入答案的,所以实现时我们还要记录区间内有多少个 (h) 是能够计入答案的,每次修改时要对这个东西进行单点修改
(O((n+q)log a))
代码
#include <bits/stdc++.h>
#define p2 p << 1
#define p3 p << 1 | 1
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
const int N = 5e5 + 5, M = 4e6 + 17, L = 1e6;
int n, q, a[N], add[M], pts[M];
bool is[M];
struct elem
{
int val, cnt;
friend inline elem operator + (elem a, elem b)
{
if (a.val < b.val) return a;
else if (a.val > b.val) return b;
return (elem) {a.val, a.cnt + b.cnt};
}
} T[M];
void build(int l, int r, int p)
{
T[p].val = add[p] = 0;
if (l == r) return (void) (T[p].cnt = pts[p] = is[l]);
int mid = l + r >> 1;
build(l, mid, p2); build(mid + 1, r, p3);
T[p].cnt = pts[p] = pts[p2] + pts[p3];
}
void down(int p)
{
add[p2] += add[p]; add[p3] += add[p];
T[p2].val += add[p]; T[p3].val += add[p];
add[p] = 0;
}
void modify(int l, int r, int pos, int v, int p)
{
if (l == r) return (void) (pts[p] += v, T[p].cnt += v);
int mid = l + r >> 1; down(p);
if (pos <= mid) modify(l, mid, pos, v, p2);
else modify(mid + 1, r, pos, v, p3);
T[p] = T[p2] + T[p3];
}
void change(int l, int r, int s, int e, int v, int p)
{
if (e < l || s > r) return;
if (s <= l && r <= e) return (void) (add[p] += v, T[p].val += v);
int mid = l + r >> 1; down(p);
change(l, mid, s, e, v, p2); change(mid + 1, r, s, e, v, p3);
T[p] = T[p2] + T[p3];
}
elem ask(int l, int r, int s, int e, int p)
{
if (e < l || s > r) return (elem) {-1, 0};
if (s <= l && r <= e) return T[p];
int mid = l + r >> 1; down(p);
return ask(l, mid, s, e, p2) + ask(mid + 1, r, s, e, p3);
}
int main()
{
int pos, x; read(n); read(q);
for (int i = 1; i <= n; i++) read(a[i]), is[a[i]] = 1;
build(1, L, 1);
for (int i = 1; i < n; i++) if (a[i] < a[i + 1])
change(1, L, a[i] + 1, a[i + 1], 1, 1);
while (q--)
{
read(pos); read(x);
modify(1, L, a[pos], -1, 1); modify(1, L, x, 1, 1);
if (pos > 1)
{
if (a[pos - 1] < a[pos]) change(1, L, a[pos - 1] + 1, a[pos], -1, 1);
if (a[pos - 1] < x) change(1, L, a[pos - 1] + 1, x, 1, 1);
}
if (pos < n)
{
if (a[pos] < a[pos + 1]) change(1, L, a[pos] + 1, a[pos + 1], -1, 1);
if (x < a[pos + 1]) change(1, L, x + 1, a[pos + 1], 1, 1);
}
a[pos] = x; printf("%d
", T[1].val ? 0 : T[1].cnt);
}
return 0;
}
I
题意
给定一个 (2^k imes 2^k) 的正整数矩阵 (a),每个数在 ([0,2^{60})) 内,行列从 (0) 开始标号
还有一个大小为 (t) 的二元组集合 (S={(x,y)}),里面的元素互不相同且 (1le x,yle 2^k)
每次操作可以选择一个位置 ((u,v)) 和一个数 (p),对于每个 ((x,y)in S),让矩阵 (((u+x)mod 2^k,(v+y)mod 2^k)) 位置上的数异或上 (p)
求把所有的数都变成 (0) 的最少操作次数
(1le kle 9,1le tlemin(99,4^k)) 且 (t) 是奇数
时限
5s
做法:异或 + 分治
妙啊!!!( imes 2)
为了方便讨论,先把二元组集合内的横纵坐标都减一
设数组 (f_{i,j}=sum_{(x,y)in S}a_{(i-x)mod2^k,(j-y)mod2^k})
性质 (1):当 (t) 为奇数时,(a) 全 (0) 当且仅当 (f) 全 (0)
证明咕咕咕
(我还没看),可以看原题解性质 (2):对 ((i,j)) 进行异或 (p) 操作的影响是对于每个 ((x,y)in S) 使 (f_{(i+2x)mod2^k,(j+2y)mod2^k}) 异或上 (p)
首先直观地描述,对 ((i,j)) 进行异或 (p) 操作相当于在 (S) 中分两次,每次选一个二元组,设这两个二元组分别为 ((x,y)) 和 ((u,v)),让 (f_{(i+x+u)mod2^k,(j+y+v)mod2^k}) 异或上 (p)
若 ((x,y) e(u,v)),则两次分别选出 ((x,y)(u,v)) 和分别选出 ((u,v)(x,y)) 的影响是一样的,根据异或运算的性质 (pigotimes p=0),可以得到如果 ((x,y) e(u,v)) 的情况不造成影响
于是只剩下了 ((x,y)=(u,v)) 的情况,( ext{Q.E.D.})
观察性质 (2),容易发现如果把矩阵 (f) 按照行列号的奇偶性拆成 (4) 个部分,那么每个部分是独立的,可以分别递归做
递归到 (k=0) 的时候判断矩阵的唯一元素是否为 (0) 即可计入答案
(O(4^kkt))
异或真的是最美妙的运算啊。
代码
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
if (bo) res = ~res + 1;
}
typedef long long ll;
typedef std::vector<std::pair<int, int> > vp;
typedef std::vector<ll> vl;
typedef std::vector<vl> vvl;
const int N = 520;
int k, t, ans;
vl emp;
vvl a;
vp op;
vvl trans(vvl a, vp op)
{
int n = a.size();
vvl res;
for (int i = 0; i < n; i++)
{
res.push_back(emp);
for (int j = 0; j < n; j++)
{
res[i].push_back(0);
for (int k = 0; k < t; k++)
res[i][j] ^= a[i - op[k].first + n & n - 1]
[j - op[k].second + n & n - 1];
}
}
return res;
}
void jjd(vvl a, vp op)
{
if (a.size() == 1) return (void) (ans += (a[0][0] > 0));
a = trans(a, op); int n = a.size();
vvl b[2][2];
for (int i = 0; i < n; i++)
{
b[i & 1][0].push_back(emp); b[i & 1][1].push_back(emp);
for (int j = 0; j < n; j++)
b[i & 1][j & 1][i >> 1].push_back(a[i][j]);
}
for (int i = 0; i < t; i++) op[i].first &= (n >> 1) - 1,
op[i].second &= (n >> 1) - 1;
for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++)
jjd(b[i][j], op);
}
int main()
{
read(k); ll x; int u, v;
for (int i = 0; i < (1 << k); i++)
{
a.push_back(emp);
for (int j = 0; j < (1 << k); j++)
read(x), a[i].push_back(x);
}
read(t);
for (int i = 1; i <= t; i++)
read(u), read(v), op.push_back(std::make_pair(u - 1, v - 1));
return std::cout << (jjd(a, op), ans) << std::endl, 0;
}
以上是关于[题解][Codeforces]Good Bye 2019 简要题解的主要内容,如果未能解决你的问题,请参考以下文章
Codeforces 1091 (Good Bye 2018)
Codeforces Good Bye 2017 C. New Year and Curling 几何枚举