GoodCoder666的个人博客

KYOCERA Programming Contest 2021 (AtCoder Beginner Contest 200) A~E 题解

2021-06-10 · 12 min read
C++ 算法竞赛

A - Century

题目大意

公元NN年在第几个世纪中?

一个世纪是由100100个年份组成的一个区间。如,11世纪为[1,100][1,100]年,22世纪为[101,200][101,200]年,……

1N30001\le N\le 3000

输入格式

NN

输出格式

将答案输出为一个整数。

样例

NN 输出
20212021 2121
200200 22

分析

根据本题条件我们可以推出:公元NN年在世纪N100\lceil \frac N {100}\rceil

代码

#include <cstdio>
using namespace std;

int main()
{
	int n;
	scanf("%d", &n);
	printf("%d\n", n % 100 == 0? n / 100: n / 100 + 1);
	return 0;
}

B - 200th ABC-200

题目大意

对于整数NN,执行KK次如下操作:

  • 如果NN200200的倍数,将NN除以200200
  • 否则,在NN后面添上200200。(如,123123变为123200123200)。

1N1051\le N\le 10^5
1K201\le K\le 20

输入格式

N KN~K

输出格式

输出最终的NN

样例

NN KK 输出
20212021 44 5053150531
4000040000 22 11
86918691 2020 8487548828184875488281

分析

本题我们按照题意模拟即可,但我们需要证明答案不会超过26312^{63}-1,这样才能使用long long

  • 任何数NN添上200200都是200200的倍数。证明:一个数只要是10010022的公倍数,它就是200200的倍数。NN添上200200后以00结尾,就证明了它是200200的倍数。
  • 这样,NN每次添上200200后都要除以200200,相当于去掉两个零再将NN除以22
  • 所以,NN每次最多增加一位,还经常减少位数(除以200200),肯定严格小于2632^{63}

代码

#include <cstdio>
using namespace std;

int main()
{
	long long n;
	int k;
	scanf("%lld%d", &n, &k);
	while(k--)
		n = n % 200LL == 0LL? n / 200LL: n * 1000LL + 200LL;
	printf("%lld\n", n);
	return 0;
}

C - Ringo's Favorite Numbers 2

题目大意

给定序列A=(A1,A2,,AN)A=(A_1,A_2,\dots,A_N),找到符合下列条件的所有(i,j)(i,j)

  • 1i<jN1\le i < j\le N
  • AiAj|A_i-A_j|200200的倍数。

2N2×1052\le N\le 2\times 10^5
1Ai1091\le A_i\le 10^9

输出格式

NN
A1 A2  ANA_1~A_2~\dots~A_N

样例

AA 输出
(123,223,123,523,200,2000)(123,223,123,523,200,2000) 44
(1,2,3,4,5)(1,2,3,4,5) 00
(199,100,200,400,300,500,600,200)(199,100,200,400,300,500,600,200) 99

分析

首先,最容易想到的O(n2)\mathcal O(n^2)的暴力算法肯定不行,因为2N2×1052\le N\le 2\times 10^5
我们考虑用桶优化:
我们有200200个桶,分别如下:

桶的编号 元素11 元素22 元素33 元素44 …… 元素除以200200的余数
00 00 200200 400400 600600 …… 00
11 11 201201 401401 601601 …… 11
22 22 202202 402402 602602 …… 22
…… …… …… …… …… …… ……
198198 198198 398398 598598 798798 …… 198198
199199 199199 399399 599599 799799 …… 199199
这时,我们发现,每个桶中的每个元素都能与这个同种的其他元素组成一对,所以我们只要在将元素加入桶中前将答案加上桶目前的大小即可。

总时间复杂度O(n)\mathcal O(n)

代码

我们的桶中不需要真正的元素,只需要记录桶的大小即可。
注意:答案的数据类型一定要用long long

#include <cstdio>
#define MOD 200
using namespace std;

int cnt[MOD];

int main()
{
	int n;
	scanf("%d", &n);
	long long ans = 0LL;
	while(n--)
	{
		int x;
		scanf("%d", &x);
		ans += cnt[x %= MOD] ++;
	}
	printf("%lld\n", ans);
	return 0;
}

D - Happy Birthday! 2

题目大意

给定序列A=(A1,A2,,AN)A=(A_1,A_2,\dots,A_N)。你要从中选出两个子序列BBCC(下标不同,不要求连续),使得BC(mod200)\sum B\equiv \sum C\pmod{200}

2N2002\le N\le 200
1Ai1091\le A_i\le 10^9

输入格式

NN
A1 A2  ANA_1~A_2~\dots~A_N

输出

如果没有合法的BBCC,输出No
如果有合法的BBCC,按下列格式输出,其中xxBB的长度、yyCC的长度,BB'BB中元素对应的下标,CC'CC中元素对应的下标:
Yes\text{Yes}
x B1 B2  Bxx~B'_1~B'_2~\dots~B'_x
y C1 C2  Cyy~C'_1~C'_2~\dots~C'_y

样例

略,请自行前往AtCoder查看

分析

我们可以证明,只要N8N\ge 8,那么就一定有解。证明如下:

  • 88个元素能组成的子序列有281=2552^8-1=255种。(每个元素可以选或不选,去掉全不选的情况)
  • 根据抽屉原理,我们将这255255种子序列按照他们除以200200的余数分别放入抽屉中,则至少有两个子序列在一个抽屉中,即必定有合法的AABB

N<8N<8时,我们暴力枚举所有可能;
N8N\ge8时,我们暴力枚举其中任意88个元素组成的所有可能即可找到解。

代码

#include <cstdio>
#include <vector>
#define maxn 10
#define MOD 200
using namespace std;

int a[maxn];
vector<int> bkt[MOD];

inline void print(const vector<int>& v)
{
	printf("%llu", v.size());
	for(int x: v)
		printf(" %d", x + 1);
	putchar('\n');
}

int main()
{
	int n;
	scanf("%d", &n);
	if(n > 8) n = 8;
	for(int i=0; i<n; i++)
		scanf("%d", a + i);
	int lim = 1 << n;
	for(int st=0; st<lim; st++)
	{
		int s = 0;
		vector<int> pos;
		for(int i=0; i<n; i++)
			if(st >> i & 1)
				s = (s + a[i]) % MOD, pos.push_back(i);
		if(!bkt[s].empty())
		{
			puts("Yes");
			print(bkt[s]);
			print(pos);
			return 0;
		}
		else bkt[s] = pos;
	}
	puts("No");
	return 0;
}

E - Patisserie ABC 2

题目大意

N3N^3个三元组(i,j,k)(i,j,k)1i,j,kN1\le i,j,k\le N),按如下排序:

  • i+j+ki+j+k小的排在前面。
  • 对于i+j+ki+j+k相同的三元组,ii小的排在前面,对于ii相同的,jj小的排在前面。

求第KK(i,j,k)(i,j,k)

1N1061\le N\le 10^6
1KN31\le K\le N^3

输入格式

N KN~K

输出格式

输出第KK(i,j,k)(i,j,k),用空格分隔。

样例

NN KK 输出
22 55 1 2 21~2~2
10000001000000 10000000000000000001000000000000000000 1000000 1000000 10000001000000~1000000~1000000
99 4747 3 1 43~1~4

分析

由于每个三元组的和都不会超过3N3N,所以我们考虑暴力枚举三元组的和,这样就能快速算出第KK个三元组的和。那么,问题来了:符合i+j+k=ni+j+k=n的三元组(i,j,k)(i,j,k)i,j,ki,j,k均不超过NN)有多少个?
这可以用容斥原理解决。首先,我们发现,上述问题实际上就是在求如下方程解的个数:

{i+j+k=n1i,j,kN\begin{cases}i+j+k=n\\1\le i,j,k\le N\end{cases}

如果我们将问题改成求如下方程解的个数(注意nnNN的区别):

{i+j+k=n1i,j,kn\begin{cases}i+j+k=n\\1\le i,j,k\le n\end{cases}

这个可以用挡板法解决,在n1n-1个位置上任选22个插入挡板,挡板分开的就是i,j,ki,j,k,则公式为Cn12C_{n-1}^2。我们设f(n)= f(n)=~上述方程解的个数(Cn12=(n1)(n2)C_{n-1}^2=(n-1)(n-2)),则总方程解的个数为f(n)f(n)
我们考虑一个、两个(不可能有三个)数大于NN的情况,这样{i+j+k=n1i,j,kN\begin{cases}i+j+k=n\\1\le i,j,k\le N\end{cases}这个方程解的个数就为f(n)+3f(n2N)3f(nN)f(n)+3f(n-2N)-3f(n-N)

代码

计算f(n)f(n)时注意特判n2n\le 2的情况,否则可能会出现负数!

#include <cstdio>
using namespace std;

using LL = long long;
int n;

inline int max(int x, int y) { return x > y? x: y; }
inline int min(int x, int y) { return x < y? x: y; }

inline LL f(LL n) { return n-- > 2? n * (n - 1LL) >> 1LL: 0LL; }
inline LL count(int s) { return f(s) - 3 * (f(s - n) - f(s - (n << 1))); }

int main()
{
	LL k;
	scanf("%d%lld", &n, &k);
	int lim = n * 3;
	for(int sum=3; sum<=lim; sum++)
	{
		LL cnt = count(sum);
		if(k > cnt) { k -= cnt; continue; }
		for(int a=1; a<=n; a++)
		{
			int minb = max(1, sum - a - n), maxb = min(n, sum - a - 1);
			if(minb > maxb) continue;
			int num = maxb - minb + 1;
			if(k <= num)
			{
				int b = minb + k - 1;
				int c = sum - a - b;
				printf("%d %d %d\n", a, b, c);
				return 0;
			}
			k -= num;
		}
	}
	return 0;
}