ABC200/KYOCERA2021 A~E
- [A - Century](https://atcoder.jp/contests/abc200/tasks/abc200_a)
- [B - 200th ABC-200](https://atcoder.jp/contests/abc200/tasks/abc200_b)
- [C - Ringo's Favorite Numbers 2](https://atcoder.jp/contests/abc200/tasks/abc200_c)
- [D - Happy Birthday! 2](https://atcoder.jp/contests/abc200/tasks/abc200_d)
- [E - Patisserie ABC 2](https://atcoder.jp/contests/abc200/tasks/abc200_e)
A - Century
题目大意
公元 N N N年在第几个世纪中?
一个世纪是由 100 100 100个年份组成的一个区间。如, 1 1 1世纪为 [ 1 , 100 ] [1,100] [1,100]年, 2 2 2世纪为 [ 101 , 200 ] [101,200] [101,200]年,……
1 ≤ N ≤ 3000 1\le N\le 3000 1≤N≤3000
输入格式
N N N
输出格式
将答案输出为一个整数。
样例
N N N | 输出 |
---|---|
2021 2021 2021 | 21 21 21 |
200 200 200 | 2 2 2 |
分析
根据本题条件我们可以推出:公元 N N N年在世纪 ⌈ N 100 ⌉ \lceil \frac N {100}\rceil ⌈100N⌉。
代码
#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
题目大意
对于整数 N N N,执行 K K K次如下操作:
- 如果 N N N是 200 200 200的倍数,将 N N N除以 200 200 200。
- 否则,在 N N N后面添上 200 200 200。(如, 123 123 123变为 123200 123200 123200)。
1
≤
N
≤
1
0
5
1\le N\le 10^5
1≤N≤105
1
≤
K
≤
20
1\le K\le 20
1≤K≤20
输入格式
N K N~K N K
输出格式
输出最终的 N N N。
样例
N N N | K K K | 输出 |
---|---|---|
2021 2021 2021 | 4 4 4 | 50531 50531 50531 |
40000 40000 40000 | 2 2 2 | 1 1 1 |
8691 8691 8691 | 20 20 20 | 84875488281 84875488281 84875488281 |
分析
本题我们按照题意模拟即可,但我们需要证明答案不会超过
2
63
−
1
2^{63}-1
263−1,这样才能使用long long
:
- 任何数
N
N
N添上
200
200
200都是
200
200
200的倍数。证明:一个数只要是
100
100
100和
2
2
2的公倍数,它就是
200
200
200的倍数。
N
N
N添上
200
200
200后以
00
结尾,就证明了它是 200 200 200的倍数。 - 这样, N N N每次添上 200 200 200后都要除以 200 200 200,相当于去掉两个零再将 N N N除以 2 2 2。
- 所以, N N N每次最多增加一位,还经常减少位数(除以 200 200 200),肯定严格小于 2 63 2^{63} 263。
代码
#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 = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,\dots,A_N) A=(A1,A2,…,AN),找到符合下列条件的所有 ( i , j ) (i,j) (i,j):
- 1 ≤ i < j ≤ N 1\le i < j\le N 1≤i<j≤N;
- ∣ A i − A j ∣ |A_i-A_j| ∣Ai−Aj∣是 200 200 200的倍数。
2
≤
N
≤
2
×
1
0
5
2\le N\le 2\times 10^5
2≤N≤2×105
1
≤
A
i
≤
1
0
9
1\le A_i\le 10^9
1≤Ai≤109
输出格式
N
N
N
A
1
A
2
…
A
N
A_1~A_2~\dots~A_N
A1 A2 … AN
样例
A A A | 输出 |
---|---|
( 123 , 223 , 123 , 523 , 200 , 2000 ) (123,223,123,523,200,2000) (123,223,123,523,200,2000) | 4 4 4 |
( 1 , 2 , 3 , 4 , 5 ) (1,2,3,4,5) (1,2,3,4,5) | 0 0 0 |
( 199 , 100 , 200 , 400 , 300 , 500 , 600 , 200 ) (199,100,200,400,300,500,600,200) (199,100,200,400,300,500,600,200) | 9 9 9 |
分析
首先,最容易想到的
O
(
n
2
)
\mathcal O(n^2)
O(n2)的暴力算法肯定不行,因为
2
≤
N
≤
2
×
1
0
5
2\le N\le 2\times 10^5
2≤N≤2×105。
我们考虑用桶优化:
我们有
200
200
200个桶,分别如下:
桶的编号 | 元素 1 1 1 | 元素 2 2 2 | 元素 3 3 3 | 元素 4 4 4 | …… | 元素除以 200 200 200的余数 |
---|---|---|---|---|---|---|
0 0 0 | 0 0 0 | 200 200 200 | 400 400 400 | 600 600 600 | …… | 0 0 0 |
1 1 1 | 1 1 1 | 201 201 201 | 401 401 401 | 601 601 601 | …… | 1 1 1 |
2 2 2 | 2 2 2 | 202 202 202 | 402 402 402 | 602 602 602 | …… | 2 2 2 |
…… | …… | …… | …… | …… | …… | …… |
198 198 198 | 198 198 198 | 398 398 398 | 598 598 598 | 798 798 798 | …… | 198 198 198 |
199 199 199 | 199 199 199 | 399 399 399 | 599 599 599 | 799 799 799 | …… | 199 199 199 |
这时,我们发现,每个桶中的每个元素都能与这个同种的其他元素组成一对,所以我们只要在将元素加入桶中前将答案加上桶目前的大小即可。 |
总时间复杂度 O ( n ) \mathcal O(n) 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 = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,\dots,A_N) A=(A1,A2,…,AN)。你要从中选出两个子序列 B B B和 C C C(下标不同,不要求连续),使得 ∑ B ≡ ∑ C ( m o d 200 ) \sum B\equiv \sum C\pmod{200} ∑B≡∑C(mod200)。
2
≤
N
≤
200
2\le N\le 200
2≤N≤200
1
≤
A
i
≤
1
0
9
1\le A_i\le 10^9
1≤Ai≤109
输入格式
N
N
N
A
1
A
2
…
A
N
A_1~A_2~\dots~A_N
A1 A2 … AN
输出
如果没有合法的
B
B
B和
C
C
C,输出No
。
如果有合法的
B
B
B和
C
C
C,按下列格式输出,其中
x
x
x为
B
B
B的长度、
y
y
y为
C
C
C的长度,
B
′
B'
B′为
B
B
B中元素对应的下标,
C
′
C'
C′为
C
C
C中元素对应的下标:
Yes
\text{Yes}
Yes
x
B
1
′
B
2
′
…
B
x
′
x~B'_1~B'_2~\dots~B'_x
x B1′ B2′ … Bx′
y
C
1
′
C
2
′
…
C
y
′
y~C'_1~C'_2~\dots~C'_y
y C1′ C2′ … Cy′
样例
略,请自行前往AtCoder查看
分析
我们可以证明,只要 N ≥ 8 N\ge 8 N≥8,那么就一定有解。证明如下:
- 8 8 8个元素能组成的子序列有 2 8 − 1 = 255 2^8-1=255 28−1=255种。(每个元素可以选或不选,去掉全不选的情况)
- 根据抽屉原理,我们将这 255 255 255种子序列按照他们除以 200 200 200的余数分别放入抽屉中,则至少有两个子序列在一个抽屉中,即必定有合法的 A A A和 B B B。
当
N
<
8
N<8
N<8时,我们暴力枚举所有可能;
当
N
≥
8
N\ge8
N≥8时,我们暴力枚举其中任意
8
8
8个元素组成的所有可能即可找到解。
代码
#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
题目大意
有 N 3 N^3 N3个三元组 ( i , j , k ) (i,j,k) (i,j,k)( 1 ≤ i , j , k ≤ N 1\le i,j,k\le N 1≤i,j,k≤N),按如下排序:
- i + j + k i+j+k i+j+k小的排在前面。
- 对于 i + j + k i+j+k i+j+k相同的三元组, i i i小的排在前面,对于 i i i相同的, j j j小的排在前面。
求第 K K K个 ( i , j , k ) (i,j,k) (i,j,k)。
1
≤
N
≤
1
0
6
1\le N\le 10^6
1≤N≤106
1
≤
K
≤
N
3
1\le K\le N^3
1≤K≤N3
输入格式
N K N~K N K
输出格式
输出第 K K K个 ( i , j , k ) (i,j,k) (i,j,k),用空格分隔。
样例
N N N | K K K | 输出 |
---|---|---|
2 2 2 | 5 5 5 | 1 2 2 1~2~2 1 2 2 |
1000000 1000000 1000000 | 1000000000000000000 1000000000000000000 1000000000000000000 | 1000000 1000000 1000000 1000000~1000000~1000000 1000000 1000000 1000000 |
9 9 9 | 47 47 47 | 3 1 4 3~1~4 3 1 4 |
分析
由于每个三元组的和都不会超过
3
N
3N
3N,所以我们考虑暴力枚举三元组的和,这样就能快速算出第
K
K
K个三元组的和。那么,问题来了:符合
i
+
j
+
k
=
n
i+j+k=n
i+j+k=n的三元组
(
i
,
j
,
k
)
(i,j,k)
(i,j,k)(
i
,
j
,
k
i,j,k
i,j,k均不超过
N
N
N)有多少个?
这可以用容斥原理解决。首先,我们发现,上述问题实际上就是在求如下方程解的个数:
{
i
+
j
+
k
=
n
1
≤
i
,
j
,
k
≤
N
\begin{cases}i+j+k=n\\1\le i,j,k\le N\end{cases}
{i+j+k=n1≤i,j,k≤N
如果我们将问题改成求如下方程解的个数(注意
n
n
n和
N
N
N的区别):
{
i
+
j
+
k
=
n
1
≤
i
,
j
,
k
≤
n
\begin{cases}i+j+k=n\\1\le i,j,k\le n\end{cases}
{i+j+k=n1≤i,j,k≤n
这个可以用挡板法解决,在
n
−
1
n-1
n−1个位置上任选
2
2
2个插入挡板,挡板分开的就是
i
,
j
,
k
i,j,k
i,j,k,则公式为
C
n
−
1
2
C_{n-1}^2
Cn−12。我们设
f
(
n
)
=
f(n)=~
f(n)= 上述方程解的个数(
C
n
−
1
2
=
(
n
−
1
)
(
n
−
2
)
C_{n-1}^2=(n-1)(n-2)
Cn−12=(n−1)(n−2)),则总方程解的个数为
f
(
n
)
f(n)
f(n)。
我们考虑一个、两个(不可能有三个)数大于
N
N
N的情况,这样
{
i
+
j
+
k
=
n
1
≤
i
,
j
,
k
≤
N
\begin{cases}i+j+k=n\\1\le i,j,k\le N\end{cases}
{i+j+k=n1≤i,j,k≤N这个方程解的个数就为
f
(
n
)
+
3
f
(
n
−
2
N
)
−
3
f
(
n
−
N
)
f(n)+3f(n-2N)-3f(n-N)
f(n)+3f(n−2N)−3f(n−N)。
代码
计算 f ( n ) f(n) f(n)时注意特判 n ≤ 2 n\le 2 n≤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;
}