[题解][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;
}
原文地址:https://www.cnblogs.com/xyz32768/p/12147147.html