GoodCoder666的个人博客

AISing Programming Contest 2021 (AtCoder Beginner Contest 202) A~E 题解

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

A - Three Dice

一个人抛了三个骰子,它们的顶面分别是a,b,ca,b,c。求它们的底面之和。
这里用的骰子是标准骰子,即两个相对的面之和为77

1a,b,c61\le a,b,c\le 6

输入格式

a b ca~b~c

输出格式

输出答案。

样例

aa bb cc 答案
11 44 33 1313
55 66 44 66

分析

因为两个相对的面之和为77,所以本题的答案为(7a)+(7b)+(7c)=21abc(7-a)+(7-b)+(7-c)=21-a-b-c

代码

#include <cstdio>
using namespace std;

int main()
{
	int a, b, c;
	scanf("%d%d%d", &a, &b, &c);
	printf("%d\n", 21 - a - b - c);
	return 0;
}

B - 180°

给定一个由01689组成的字符串SS。将其旋转180°180\degree并输出。

一个字符串旋转180°180\degree的方法:

  • 将其翻转(reverse)。
  • 将其中的6替换为99替换为6

1S1051\le |S|\le10^5

输入格式

SS

输出格式

输出SS旋转180°180\degree后的字符串。

样例

SS 输出
0601889 6881090
86910 01698
01010 01010

分析

本题直接按要求模拟即可。

代码

#include <cstdio>
#define maxn 100005
using namespace std;

char s[maxn];

int main()
{
	int n = 0;
	char c;
	while((c = getchar()) != '\n')
		s[n++] = c;
	while(n--)
		putchar(s[n] == '6'? '9': s[n] == '9'? '6': s[n]);
	putchar('\n');
	return 0;
}

C - Made Up

给定三个长度为NN的序列:A,B,CA,B,C
有多少对(i,j)(i,j)符合Ai=BCjA_i=B_{C_j}

1N1051\le N\le 10^5
1Ai,Bi,CiN1\le A_i,B_i,C_i\le N

输入格式

NN
A1 A2  ANA_1~A_2~\dots~A_N
B1 B2  BNB_1~B_2~\dots~B_N
C1 C2  CNC_1~C_2~\dots~C_N

输出格式

输出符合Ai=BCjA_i=B_{C_j}(i,j)(i,j)的对数。

样例

样例输入1

3
1 2 2
3 1 2
2 3 2

样例输出1

4

44(i,j)(i,j)符合条件:(1,1),(1,3),(2,2),(3,2)(1,1),(1,3),(2,2),(3,2)

样例输入2

4
1 1 1 1
1 1 1 1
1 2 3 4

样例输出2

16

所有(i,j)(i,j)都符合条件。

样例输入3

3
2 3 3
1 3 3
1 1 1

样例输出3

0

没有(i,j)(i,j)符合条件。

分析

我们很容易想到O(n2)O(n^2)的算法:暴力枚举所有(i,j)(i,j),并统计符合条件的对数。
可惜,这样会TLE
我们考虑将所有的AiA_iBCjB_{C_j}分别放入两个桶acnt\mathrm{acnt}bcnt\mathrm{bcnt}
根据乘法原理我们得出答案为i=1nacntibcnti\sum\limits_{i=1}^n\mathrm{acnt}_i\mathrm{bcnt}_i

代码

注意:不要忘记使用long long

#include <cstdio>
#define maxn 100005
using namespace std;

using LL = long long;
int acnt[maxn], b[maxn], bcnt[maxn];

int main()
{
	int n;
	scanf("%d", &n);
	for(int i=0; i<n; i++)
	{
		int a;
		scanf("%d", &a);
		acnt[a] ++;
	}
	for(int i=0; i<n; i++)
		scanf("%d", b + i);
	for(int i=0; i<n; i++)
	{
		int c;
		scanf("%d", &c);
		bcnt[b[--c]] ++;
	}
	LL ans = 0LL;
	for(int i=1; i<=n; i++)
		ans += LL(acnt[i]) * LL(bcnt[i]);
	printf("%lld\n", ans);
	return 0;
}

D - aab aba baa

在由AAaBBb(均不要求连续)组成的字符串中,求字典序第KK小的。

1A,B301\le A,B\le 30
1KS1\le K\le SSS为由AAaBBb组成的字符串的个数)

输入格式

A B KA~B~K

输出格式

输出由AAaBBb组成的字符串中字典序第KK小的。

样例

AA BB KK 输出
22 22 44 baab
3030 3030 118264581564861424118264581564861424 3030b+30+30a)

分析

我们令dp(a,b)\mathrm{dp}(a,b)为由aaabbb组成的字符串的个数,则:

  • 我们在长度为a+b1a+b-1的字符串上再添上一个ab
  • dp(a,b)=dp(a1,b)+dp(a,b1)\mathrm{dp}(a,b)=\mathrm{dp}(a-1,b)+\mathrm{dp}(a,b-1)

我们令f(a,b,k)f(a,b,k)为由AAaBBb组成的字符串中字典序第KK小的字符串,则有如下递推式(这里的加法表示字符串连接):

f(a,b,k)={"(a=b=0)a"+f(a1,b,k)(b=0)b"+f(a,b1,k)(a=0)a"+f(a1,b,k)(kdp(a1,b))b"+f(a,b1,kdp(a1,b))(k>dp(a1,b))f(a,b,k)=\begin{cases} ``" & (a=b=0)\\ ``a"+f(a-1,b,k) & (b=0)\\ ``b"+f(a,b-1,k) & (a=0)\\ ``a"+f(a-1,b,k) & (k\le \mathrm{dp}(a-1,b))\\ ``b"+f(a,b-1,k- \mathrm{dp}(a-1,b)) & (k>\mathrm{dp}(a-1,b)) \end{cases}

代码

写代码时,可以用递归形式,也可以使用非递归形式(更快):

#include <cstdio>
#define maxn 35
using namespace std;

using LL = long long;
LL dp[maxn][maxn];

int main()
{
	int a, b;
	LL k;
	scanf("%d%d%lld", &a, &b, &k);
	for(int i=0; i<=a; i++) dp[i][0] = 1;
	for(int i=0; i<=b; i++) dp[0][i] = 1;
	for(int i=1; i<=a; i++)
		for(int j=1; j<=b; j++)
			dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
	while(a && b)
	{
		LL t = dp[a - 1][b];
		if(k <= t) putchar('a'), a --;
		else putchar('b'), b --, k -= t;
	}
	while(a--) putchar('a');
	while(b--) putchar('b');
	putchar('\n');
	return 0;
}

E - Count Descendants

我们有一棵NN个节点的树,节点的编号分别为1,2,,N1,2,\dots,N
11号点是根节点,且第ii个点(2iN2\le i\le N)的父亲节点是PiP_i
给你QQ个查询,第ii个查询包含两个整数UiU_iDiD_i,求符合下列条件的点uu的个数:

  • uu到根节点的最短路径正好DiD_i条边;
  • UiU_iuu到根节点的最短路径中(包含两端)。

1N2×1051\le N\le 2\times10^5
1Pi<i1\le P_i < i
1Q2×1051\le Q\le 2\times10^5
1UiN1\le U_i\le N
0Di<N0\le D_i < N

输入格式

NN
P2 P3  PNP_2~P_3~\dots~P_N
QQ
U1 D1U_1~D_1
U2 D2U_2~D_2
\vdots
UQ DQU_Q~D_Q

输出格式

输出QQ行。第ii行包含对第ii个查询的回应。

样例

样例输入

7
1 1 2 2 4 2
4
1 2
7 2
4 1
5 5

样例输出

3
1
0
0

在第一个查询中,节点4,5,74,5,7符合条件。
在第二个查询中,只有节点77符合条件。
在最后两个查询中,没有节点符合条件。
样例说明

分析

我们可以先在整棵树上从根节点开始跑一遍DFS\text{DFS},对于节点ii预处理出ini\mathrm{in}_iouti\mathrm{out}_i,分别表示进入和走出这个节点的时间,同时将第ii层节点的所有in\mathrm{in}放入depini\mathrm{depin}_i
如果节点uu到根节点的路径中有vv,则invinu<outv\mathrm{in}_v\le\mathrm{in}_u < \mathrm{out}_v
因此,对于每个查询,我们利用二分查找即可快速算出符合条件的节点个数。

代码

#include <cstdio>
#include <vector>
#include <algorithm>
#define maxn 200005
using namespace std;

int in[maxn], out[maxn], dep[maxn], cnt;
vector<int> G[maxn], depin[maxn];

void dfs(int v)
{
	depin[dep[v]].push_back(in[v] = cnt++);
	for(int u: G[v])
		dfs(u);
	out[v] = cnt++;
}

int main()
{
	int n;
	scanf("%d", &n);
	dep[0] = cnt = 0;
	for(int i=1; i<n; i++)
	{
		int p;
		scanf("%d", &p);
		dep[i] = dep[--p] + 1;
		G[p].push_back(i);
	}
	dfs(0);
	int q;
	scanf("%d", &q);
	while(q--)
	{
		int u, d;
		scanf("%d%d", &u, &d);
		const auto& din = depin[d];
		auto first = lower_bound(din.begin(), din.end(), in[--u]);
		auto last = lower_bound(din.begin(), din.end(), out[u]);
		printf("%lld\n", last - first);
	}
	return 0;
}