GoodCoder666的个人博客

AtCoder Beginner Contest 203 (Sponsored by Panasonic) A~E 题解

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

A - Chinchirorin

题目大意

给定三个整数a,b,ca,b,c,如果它们中有两个相等,输出另一个;否则,输出00

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

输入格式

a b ca~b~c

输出格式

如果a,b,ca,b,c中有两个相等,输出另一个;否则,输出00

样例

aa bb cc 输出
22 55 22 55
44 55 66 00
11 11 11 11

分析

AA题还是一如既往的水……直接暴力判断三种相等的情况即可。

代码

#include <cstdio>
using namespace std;

int main()
{
	int a, b, c;
	scanf("%d%d%d", &a, &b, &c);
	if(a == b) printf("%d\n", c);
	else if(b == c) printf("%d\n", a);
	else if(a == c) printf("%d\n", b);
	else puts("0");
	return 0;
}

B - AtCoder Condominium

题目大意

给定NNKK,求i=1Nj=1Ki0j\sum\limits_{i=1}^N\sum\limits_{j=1}^K \overline{i0j}
1N,K91\le N,K\le 9

输入格式

N KN~K

输出格式

输出i=1Nj=1Ki0j\sum\limits_{i=1}^N\sum\limits_{j=1}^K \overline{i0j}

样例

NN KK 输出
11 22 203203
33 33 18181818

分析

本题可以直接暴力,但我使用的是如下O(1)\mathcal O(1)算法:
根据i0j=100i+j\overline{i0j}=100i+j1+2++N=N(N+1)21+2+\dots+N=\frac{N(N+1)}2,则有如下推导:

  • i=1Nj=1Ki0j=i=1Nj=1K100i+i=1Nj=1Kj\sum\limits_{i=1}^N\sum\limits_{j=1}^K \overline{i0j}=\sum\limits_{i=1}^N\sum\limits_{j=1}^K 100i+\sum\limits_{i=1}^N\sum\limits_{j=1}^K j
  • i=1Nj=1K100i=i=1N100iK=100Ki=1Ni=100N(N+1)K2\sum\limits_{i=1}^N\sum\limits_{j=1}^K 100i=\sum\limits_{i=1}^N 100iK=100K\sum\limits_{i=1}^N i=\frac{100N(N+1)K}2
  • i=1Nj=1Kj=i=1NK(K+1)2=K(K+1)N2\sum\limits_{i=1}^N\sum\limits_{j=1}^K j=\sum\limits_{i=1}^N\frac{K(K+1)}2=\frac{K(K+1)N}2
  • i=1Nj=1Ki0j=100N(N+1)K2+K(K+1)N2=100N(N+1)K+K(K+1)N2\sum\limits_{i=1}^N\sum\limits_{j=1}^K \overline{i0j}=\frac{100N(N+1)K}2+\frac{K(K+1)N}2=\frac{100N(N+1)K+K(K+1)N}2

这样,我们就可以直接通过公式100N(N+1)K+K(K+1)N2\frac{100N(N+1)K+K(K+1)N}2计算出结果了。

代码

#include <cstdio>
using namespace std;

inline int sum(int x) { return x * (x + 1) >> 1; }

int main()
{
	int n, k;
	scanf("%d%d", &n, &k);
	printf("%d\n", sum(n) * k * 100 + sum(k) * n);
	return 0;
}

C - Friends and Travel costs

题目大意

10100+110^{100}+1个村庄,分别为村庄0,1,,101000,1,\dots,10^{100},相邻两个村庄之间的过路费是11元。
Taro一开始有KK元且在村庄00。他想要到达编号尽可能大的村庄。
他有NN个朋友。第ii个朋友会在Taro到达村庄AiA_i时给他BiB_i元。
求Taro最后到达的村庄的编号。

1N2×1051\le N\le 2\times10^5
1K1091\le K\le 10^9
1Ai10181\le A_i\le 10^{18}
1Bi1091\le B_i\le 10^9

输入格式

N KN~K
A1 B1A_1~B_1
A2 B2A_2~B_2
\dots
AN BNA_N~B_N

输出

输出Taro最后到达的村庄的编号。

样例

样例输入1

2 3
2 1
5 10

样例输出1

4

样例输入2

5 1000000000
1 1000000000
2 1000000000
3 1000000000
4 1000000000
5 1000000000

样例输出2

6000000000

请不要使用3232位整数。

样例输入3

3 2
5 5
2 1
2 2

样例输出3

10

Taro在一个村庄可能有多个朋友。

分析

根据题目中的数据范围,我们可以证明答案严格小于2642^{64},所以我们使用unsigned long long作为存储数据类型。
可是,由于村庄数量还是太多,我们仍然无法依次模拟到达每个村庄。
我们发现NN较小,所以我们可以从朋友的角度考虑。
我们可以按AiA_i排序所有朋友(BiB_i的顺序不重要),这样就能把整个行程形成分成若干个区间,并依次判断每个区间是否能走完即可。

代码

注意:我这里排序使用的是优先队列(priority_queue

#include <cstdio>
#include <queue>
#define maxn 200005
#define INF 18446744073709551615ULL
using namespace std;

using ULL = unsigned long long;
using pll = pair<ULL, ULL>;

int main()
{
	int n;
	ULL k;
	scanf("%d%llu", &n, &k);
	priority_queue<pll, vector<pll>, greater<pll> > q;
	for(int i=0; i<n; i++)
	{
		ULL a, b;
		scanf("%llu%llu", &a, &b);
		q.emplace(a, b);
	}
	ULL lastv = 0ULL;
	q.emplace(INF, 0ULL);
	while(!q.empty())
	{
		auto [a, b] = q.top(); q.pop();
		ULL cost = a - lastv;
		if(k < cost)
		{
			printf("%llu\n", lastv + k);
			return 0;
		}
		k -= cost;
		lastv = a, k += b;
	}
	return 0;
}

D - Pond

题目大意

给定一个N×NN\times N的正方形矩阵AA,第ii行第jj列的元素是Ai,jA_{i,j}
AA中所有的K×KK\times K的子矩阵的中间值的最小值。
一个K×KK\times K的正方形的中间值为其中第(K22+1)(\left\lfloor\frac{K^2}2\right\rfloor+1)大的值。

1KN8001\le K\le N\le 800
1Ai,j1091\le A_{i,j}\le 10^9

如果不能理解题意,请看下图:

对应的输入输出:

3 2
5 9 8
2 1 3
7 4 6

/

2

输入格式

N KN~K
A1,1 A1,2  A1,NA_{1,1}~A_{1,2}~\dots~A_{1,N}
A2,1 A2,2  A2,NA_{2,1}~A_{2,2}~\dots~A_{2,N}
AN,1 AN,2  AN,NA_{N,1}~A_{N,2}~\dots~A_{N,N}

输出格式

输出答案。

样例

样例输入1

3 2
1 7 0
5 8 11
10 4 2

样例输出1

4

N=3     K=2A=[17058111042]N=3~~~~~K=2\\ A=\begin{bmatrix} 1 & 7 & 0\\ 5 & 8 & 11\\ 10 & 4 & 2 \end{bmatrix}

我们有四个2×22\times2的正方形:{8,7,5,1}, {11,8,7,0}, {10,8,5,4}, {11,8,4,2}\{8, 7, 5, 1\}, ~\{11,8,7,0\},~ \{10,8,5,4\}, ~\{11,8,4,2\}
我们依次从每个的元素中取第K22+1=3\left\lfloor\frac{K^2}2\right\rfloor+1=3大的:{5,7,5,4}\{5,7,5,4\}
最后,我们从{5,7,5,4}\{5,7,5,4\}中选出最小的:44

样例输入2

3 3
1 2 3
4 5 6
7 8 9

样例输出2

5

分析

本题可以二分答案。我们判定一个数是否为一个K×KK\times K的正方形的中间值时,只需要计算这个正方形内严格大于这个数的数的个数是否为K22\left\lfloor\frac{K^2}2\right\rfloor即可。
因此,我们可以使用矩阵前缀和快速计算一个正方形内严格大于一个数的数的数的个数。
总时间复杂度O(n2logmax{A})\mathcal O(n^2\log\max\{A\})

代码

#include <cstdio>
#define maxn 805
#define INF 2147483647
using namespace std;

int a[maxn][maxn], dp[maxn][maxn], n, k, target;

inline int count(int x1, int y1, int x2, int y2)
{
	return dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1];
}

inline bool check(int x)
{
	for(int i=1; i<=n; i++)
		for(int j=1; j<=n; j++)
			dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + (a[i][j] > x);
	for(int x2=k; x2<=n; x2++)
		for(int y2=k; y2<=n; y2++)
		{
			int x1 = x2 - k + 1, y1 = y2 - k + 1;
			if(count(x1, y1, x2, y2) < target)
				return true;
		}
	return false;
}

int main()
{
	scanf("%d%d", &n, &k);
	target = (k * k >> 1) + 1;
	int l = INF, r = 0, ans = 0;
	for(int i=1; i<=n; i++)
		for(int j=1; j<=n; j++)
		{
			scanf("%d", a[i] + j);
			if(a[i][j] > r) r = a[i][j];
			if(a[i][j] < l) l = a[i][j];
		}
	while(l <= r)
	{
		int mid = l + r >> 1;
		if(check(mid)) ans = mid, r = mid - 1;
		else l = mid + 1;
	}
	printf("%d\n", ans);
	return 0;
}

E - White Pawn

题目大意

有一个(2N+1)×(2N+1)(2N+1)\times(2N+1)的正方形棋盘,行数、列数下标都依次从002N2N。我们用(i,j)(i,j)表示棋盘上iijj列的位置。
我们有一颗棋子,初始位置在(0,N)(0,N)。棋盘上有MM个黑方格,第ii个的位置在(Xi,Yi)(X_i,Y_i),其余都是白方格。
当棋子在(i,j)(i,j)时,你可以执行任意下列操作,但不能移出棋盘:

  • (i+1,j)(i+1,j)是白色时,移到(i+1,j)(i+1,j)
  • (i+1,j1)(i+1,j-1)是黑色时,移到(i+1,j1)(i+1,j-1)
  • (i+1,j+1)(i+1,j+1)是黑色是,移到(i+1,j+1)(i+1,j+1)

棋盘上的方格不能移动。求棋盘的最后一行的能到达的列的个数。

1N1091\le N\le 10^9
0M2×1050\le M\le 2\times 10^5
1Xi2N1\le X_i\le 2N
0Yi2N0\le Y_i\le 2N
(Xi,Yi)(X_i,Y_i)互不相等。

输入格式

N MN~M
X1 Y1X_1~Y_1
X2 Y2X_2~Y_2
\vdots
XM YMX_M~Y_M

输出格式

输出棋盘的最后一行的能到达的列的个数。

样例

样例输入1

2 4
1 1
1 2
2 0
4 2

样例输出1

3

我们可以将棋子移动到(4,0)(4,0)(4,1)(4,1)(4,2)(4,2),如下:

  • (0,2)(1,1)(2,1)(3,1)(4,2)(0,2)\to(1,1)\to(2,1)\to(3,1)\to(4,2)
  • (0,2)(1,1)(2,1)(3,1)(4,1)(0,2)\to(1,1)\to(2,1)\to(3,1)\to(4,1)
  • (0,2)(1,1)(2,0)(3,0)(4,0)(0,2)\to(1,1)\to(2,0)\to(3,0)\to(4,0)

我们不能移动到(4,3)(4,3)(4,4)(4,4),所以输出33

样例输入2

1 1
1 1

样例输出2

0

我们无法移动棋子。

分析

我们发现,当NN较大时,大多数行多是空着的,所以我们从每个XiX_i开始考虑。对于白色的位置(i,j)(i,j),如果不能到达(i1,j)(i-1,j),则不能到达(i,j)(i,j)。相反,对于黑色的(i,j)(i,j),如果能到达(i1,j1)(i-1,j-1)(i1,j+1)(i-1,j+1),则能到达(i,j)(i,j)
因此,我们先排序每个(Xi,Yi)(X_i,Y_i),再对于每个有黑色的行,用set维护能到达的列数,再按上述方法判断即可。

代码

#include <cstdio>
#include <set>
#include <vector>
#include <algorithm>
#pragma GCC optimize("Ofast")
using namespace std;

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	vector<pair<int, int> > black;
	black.reserve(m);
	while(m--)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		black.emplace_back(x, y);
	}
	m = black.size();
	sort(black.begin(), black.end());
	set<int> cols;
	cols.insert(n);
	for(int l=0, r=0; l<m; l=r)
	{
		while(r < m && black[r].first == black[l].first) r ++;
		vector<int> rem, add;
		for(int i=l; i<r; i++)
		{
			int y = black[i].second;
			bool ok = cols.count(y - 1) || cols.count(y + 1);
			if(cols.count(y))
			{
				if(!ok)
					rem.push_back(y);
			}
			else if(ok)
				add.push_back(y);
		}
		for(int y: rem) cols.erase(y);
		for(int y: add) cols.insert(y);
	}
	printf("%llu\n", cols.size());
	return 0;
}