GoodCoder666的个人博客

TOYOTA MOTOR CORPORATION Programming Contest 2023#1 (AtCoder Beginner Contest 298) A~G 题解

2023-04-19 · 24 min read
C++ 算法竞赛

好久没写题解了,这就来水一篇。

A - Job Interview

题目大意

给定一个长为 NN 的字符串 SS,由 o-x 组成。

判断 SS 是否符合下列条件:

  • SS 中至少有一个 o
  • SS 中没有 x

1N1001\le N\le 100

分析

签到题。直接按题意模拟即可。

代码

#include <cstdio>
using namespace std;

int main()
{
	while(getchar() != '\n');
	char c;
	bool ok = false;
	while((c = getchar()) != '\n')
	{
		if(c == 'x')
		{
			puts("No");
			return 0;
		}
		if(c == 'o')
			ok = true;
	}
	puts(ok? "Yes": "No");
	return 0;
}

Python 水题大法 速通大法

input()
s = input()
print('Yes' if 'o' in s and 'x' not in s else 'No')

成功省掉208208个字符(


B - Coloring Matrix

题目大意

给定两个 N×NN\times N 的矩阵 AABB,都由 0011 组成。

你可以将 AA 顺时针旋转 0°,90°,180°0\degree,90\degree,180\degree270°270\degree(任选其一)。

判断旋转后的 AA 能否满足:

  • 对于每个 Ai,j=1A_{i,j}=1(i,j)(i,j)Bi,j=1B_{i,j}=1

1N1001\le N\le 100

分析

原题中还贴心的给出了如何将一个矩阵旋转90°90\degree,照题意模拟,旋转44次并逐个判断即可。

代码

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

int a[maxn][maxn], b[maxn][maxn], c[maxn][maxn];

int main()
{
	int n;
	scanf("%d", &n);
	for(int i=0; i<n; i++)
		for(int j=0; j<n; j++)
			scanf("%d", a[i] + j);
	for(int i=0; i<n; i++)
		for(int j=0; j<n; j++)
			scanf("%d", b[i] + j);
	for(int x=0; x<4; x++)
	{
		for(int i=0; i<n; i++)
			for(int j=0; j<n; j++)
				c[i][j] = a[i][j];
		for(int i=0; i<n; i++)
			for(int j=0; j<n; j++)
				a[i][j] = c[n - 1 - j][i];
		for(int i=0; i<n; i++)
			for(int j=0; j<n; j++)
				if(a[i][j] && !b[i][j])
					goto bad; // goto 不是好习惯(改不掉了),千万不要学
		puts("Yes");
		return 0;
		bad:;
	}
	puts("No");
	return 0;
}

C - Cards Query Problem

题目大意

NN 个盒子,编号 1N1\sim N,初始均为空。依次处理 QQ 次询问:

  • 1 i j:将数字 ii 写在一张空卡牌上,放入盒子 jj
  • 2 i:按升序输出盒子 ii 中的所有卡牌(允许重复)。
  • 3 i:按升序输出包含卡牌 ii 的所有盒子的编号。若一个盒子里有多张卡牌 ii,则这个盒子的编号仅输出一次。

1N,Q2×1051\le N,Q\le 2\times 10^5

对于查询中所有卡牌上的数字 xx,均有 1x2×1051\le x\le 2\times 10^5
对于查询中所有的盒子编号 yy,均有 1yN1\le y\le N

题目保证输出不超过 2×1052\times 10^5 个整数。

分析

我们分别考虑两种输出操作的做法。

  • 2 i:很容易想到,既然要按升序输出,并且允许重复,我们可以使用 NNmultiset 来依次存储每个盒子中的卡牌,处理操作 11 时更新。
  • 3 i:首先不能从 NN 个盒子中依次查找,这样明显会 TLE。正确的做法是,使用 2×1052\times 10^5set(注意不能重复,所以不用 multiset)分别存储每张卡牌所在的箱子编号,处理操作 11 时更新。

此外,本题也可以使用 priority_queuemap,甚至直接输出时排序并去重,不过使用 set 的方式是最简单、代码量最少的。几种方法的总时间复杂度都是 O(QlogN)\mathcal O(Q\log N)

代码

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

multiset<int> box[maxn];
set<int> has[maxn];

int main()
{
	int n, q;
	scanf("%d%d", &n, &q);
	while(q--)
	{
		int op, i;
		scanf("%d%d", &op, &i);
		if(op == 1)
		{
			int j;
			scanf("%d", &j);
			box[j].insert(i);
			has[i].insert(j);
		}
		else if(op == 2)
		{
			for(int x: box[i])
				printf("%d ", x);
			putchar('\n');
		}
		else if(op == 3)
		{
			for(int x: has[i])
				printf("%d ", x);
			putchar('\n');
		}
	}
	return 0;
}

D - Writing a Numeral

题目大意

我们有一个字符串 SS。初始时,S=S= 1

处理如下 QQ 次询问:

  • 1 x:将数字 xx 追加至 SS 的最后面。保证 x{1,2,3,4,5,6,7,8,9}x \in \{1,2,3,4,5,6,7,8,9\}
  • 2:删除 SS 的第一个字符。保证此时 S>1|S|>1
  • 3:输出 SS 在十进制中对应的数字,对 998244353998244353 取模。

1Q6×1051\le Q\le 6\times 10^5

分析

首先,我们必须使用一个 queuedeque 来存储字符串 SS。然后,为了在 O(1)\mathcal O(1) 的时间内处理第三种操作,我们必须维护 Smod(P=998244353)S \bmod (P=998244353) 的值,记为 AA。下面考虑前两种操作对 AA 的影响:

  • 1 x:只需在十进制中腾出一位 00 再加上 xx 即可,可表示为 A(10A+x)modPA \leftarrow (10A+x)\bmod P
  • 2:先从队列中取出 SS 的第一位,记为 xx。我们需要从 AA 中减掉最高位乘上其在十进制中的权值,即 A(A10Sx)modPA \leftarrow (A-10^{|S|}x)\bmod P(此时 S|S| 表示队列取出前一位后的长度,等同于取出前的 S1|S|-1

对于 10n10^n 的计算,我们可以用一个变量实时维护 10SmodP10^{|S|}\bmod P 的值,也可以预处理出所有 10nmodP10^n \bmod P,或者直接使用快速幂。

总时间复杂度为 O(QlogQ)\mathcal O(Q\log Q)(快速幂)或 O(Q)\mathcal O(Q)(预处理)。

代码

实现 11:使用 AtCoder Library + 快速幂,队列使用 deque

#include <cstdio>
#include <deque>
#include <atcoder/modint>
using namespace std;

using modint = atcoder::modint998244353;

int main()
{
	deque<int> s;
	s.push_back(1);
	int q;
	scanf("%d", &q);
	modint ans = 1;
	while(q--)
	{
		int op;
		scanf("%d", &op);
		if(op == 1)
		{
			int x;
			scanf("%d", &x);
			s.push_back(x);
			ans = ans * 10 + x;
		}
		else if(op == 2)
		{
			int x = s.front(); s.pop_front();
			ans -= x * modint(10).pow((int)s.size());
		}
		else printf("%d\n", ans.val());
	}
	return 0;
}

实现 22:用变量维护 10SmodP10^{|S|} \bmod P 的值,队列使用 queue

#include <cstdio>
#include <queue>
#define MOD 998244353
using namespace std;

int main()
{
	int Q;
	scanf("%d", &Q);
	queue<int> q;
	q.push(1);
	int ans = 1, p = 1;
	while(Q--)
	{
		int op;
		scanf("%d", &op);
		if(op == 1)
		{
			int x;
			scanf("%d", &x);
			q.push(x);
			ans = (ans * 10LL + x) % MOD;
			p = p * 10LL % MOD;
		}
		else if(op == 2)
		{
			ans -= (long long) q.front() * p % MOD; q.pop();
			p = p * 299473306LL % MOD; // 299473306 是 10 对于 MOD 的逆元,这句话相当于把 p 除以 10 
			if(ans < 0) ans += MOD;
		}
		else printf("%d\n", ans);
	}
	return 0;
}

E - Unfair Sugoroku

题目大意

Takahashi 和 Aoki 将玩一个游戏。游戏规则如下:

  • 游戏棋盘有 NN 个点(编号 1N1\sim N),两玩家轮流投骰子并前进。
  • Takahashi 初始在点 AA,Aoki 初始在点 BB
  • Takahashi 的骰子等概率出现 1,2,,P1,2,\dots,P,Aoki 的骰子等概率出现 1,2,,Q1,2,\dots,Q
  • 当一个玩家当前在点 xx 且骰子出现 ii 时,他移动到点 min(x+i,N)\min(x+i,N)
  • 先到达点 NN 的玩家胜利。

假定 Takahashi 先行,求他赢的概率,对 998244353998244353 取模。

2N1002\le N\le 100
1A,BN1\le A,B\le N
1P,Q101\le P,Q\le 10

分析

自己的赛时解法太复杂了,这里介绍官方题解的做法。

考虑概率 DP(下面用 Ta 表示 Takahashi,Ao 表示 Aoki):

  • fi,jf_{i,j} 表示 Ta 在点 ii,Ao 在点 jj,下一轮 Ta 移动时 Ta 获胜的概率。
  • gi,jg_{i,j} 表示 Ta 在点 ii,Ao 在点 jj,下一轮 Ao 移动时 Ta 获胜的概率。

首先考虑初始状态。根据游戏规则,对于任意 1i<n1\le i<nfn,i=gn,i=1,fi,n=gi,n=0f_{n,i}=g_{n,i}=1,f_{i,n}=g_{i,n}=0

转移也很显然:

  • 对于 Ta 当前走的每种可能的步数 k=1,2,,Pk=1,2,\dots,P,有 fi,j:=fi,j+1Pgmin(i+k,N),jf_{i,j}:=f_{i,j}+\frac1Pg_{\min(i+k,N),j}
  • 对于 Ao 当前走的每种可能的步数 k=1,2,,Qk=1,2,\dots,Q,有 gi,j:=gi,j+1Qfi,min(j+k,N)g_{i,j}:=g_{i,j}+\frac1Qf_{i,\min(j+k,N)}

整理上面的式子,得到:

fi,j={0(j=N)1(i=N)1Pk=1Pgmin(i+k,N),j(i,jN) gi,j={0(j=N)1(i=N)1Qk=1Qfi,min(j+k,N)(i,jN)f_{i,j}=\begin{cases} 0 & (j=N)\\ 1 & (i=N)\\ \frac1P\sum\limits_{k=1}^Pg_{\min(i+k,N),j} & (i,j\ne N) \end{cases}\\ ~\\ g_{i,j}=\begin{cases} 0 & (j=N)\\ 1 & (i=N)\\ \frac1Q\sum\limits_{k=1}^Qf_{i,\min(j+k,N)} & (i,j\ne N) \end{cases}\\

这里注意,由于 i=j=Ni=j=N 的情况无意义(不可能达到),所以无需特殊考虑。

最终输出结果即为 fp,qf_{p,q}。总时间复杂度为 O(N2(P+Q))\mathcal O(N^2(P+Q))。使用前缀和可以优化到 O(N2)\mathcal O(N^2),有兴趣的可以自己尝试,这里不详细解释了。

代码

#include <cstdio>
#include <algorithm>
#define MOD 998244353
#define maxn 105
using namespace std;

using LL = long long;
inline LL inv(LL x) // x ^ (MOD - 2) % MOD
{
	int y = MOD - 2;
	LL res = 1LL;
	while(y)
	{
		if(y & 1) (res *= x) %= MOD;
		(x *= x) %= MOD, y >>= 1;
	}
	return res;
}

inline void add(int& x, int y)
{
	if((x += y) >= MOD)
		x -= MOD;
}

int f[maxn][maxn], g[maxn][maxn];

int main()
{
	int n, a, b, p, q;
	scanf("%d%d%d%d%d", &n, &a, &b, &p, &q);
	for(int i=0; i<n; i++)
		f[n][i] = g[n][i] = 1, f[i][n] = g[i][n] = 0;
	LL prob_p = inv(p), prob_q = inv(q);
	for(int i=n-1; i>=a; i--)
		for(int j=n-1; j>=b; j--)
		{
			for(int k=1; k<=p; k++)
				add(f[i][j], g[min(i + k, n)][j]);
			f[i][j] = f[i][j] * prob_p % MOD;
			for(int k=1; k<=q; k++)
				add(g[i][j], f[i][min(j + k, n)]);
			g[i][j] = g[i][j] * prob_q % MOD;
		}
	printf("%d\n", f[a][b]);
	return 0;
}

F - Rook Score

题目大意

有一个 109×10910^9\times 10^9 的网格。令 (i,j)(i,j) 表示第 iijj 列的格子(1i,j1091\le i,j\le 10^9)。

对于 i=1,2,,Ni=1,2,\dots,N,整数 xix_i 被写在 (ri,ci)(r_i,c_i) 上。在剩余的 1018N10^{18}-N 个格子里只有数字 00

你可以选择一个格子 (R,C)(R,C) 并计算与其同行或同列的 2×10912\times 10^9-1 个整数之和 SS

求最大可能的 SS

1N2×1051\le N\le 2\times 10^5
1ri,ci,xi1091\le r_i,c_i,x_i\le 10^9
(ri,ci)(rj,cj)      (ij)(r_i,c_i)\ne (r_j,c_j)~~~~~~(i\ne j)

分析

我们令 f(R,C)f(R,C) 表示对于 (R,C)(R,C)SS,令 rsR\mathrm{rs}_R 表示第 RR 行的整数之和,csC\mathrm{cs}_C表示第 CC 列的整数之和,AR,CA_{R,C} 表示 (R,C)(R,C) 上的整数。

容易发现,f(R,C)=rsR+csCAR,Cf(R,C)=\mathrm{rs}_R+\mathrm{cs}_C-A_{R,C}

然后证明当 f(R,C)f(R,C) 最大时,rsR,csC0\mathrm{rs}_R,\mathrm{cs}_C\ne0

  • rsR=csC=0\mathrm{rs}_R=\mathrm{cs}_C=0,则 f(r0,c0)=x0>0=f(R,C)f(r_0,c_0)=x_0>0=f(R,C),所以 (R,C)(R,C) 不是最优解;
  • rsR0,csC=0\mathrm{rs}_R\ne0,\mathrm{cs}_C=0,则 f(R,c0)=rsR+csc0AR,c0f(R,C)=rsRf(R,c_0)=\mathrm{rs}_R+\mathrm{cs}_{c_0}-A_{R,c_0}\ge f(R,C)=\mathrm{rs}_R,所以 (R,C)(R,C) 不是最优解(或有多个最优解,但其中至少有一个解 (x,y)(x,y) 使得 rsx,csy0\mathrm{rs}_x,\mathrm{cs}_y\ne0
  • rsR=0,csC0\mathrm{rs}_R=0,\mathrm{cs}_C\ne0 同理。

所以,我们可以依次考虑每一行 RRrsR0\mathrm{rs}_R\ne 0),相当于固定了 rsR\mathrm{rs}_R。这时,我们只需找到一列 CCcsC0\mathrm{cs}_C\ne 0),使得 csCAR,C\mathrm{cs}_C-A_{R,C} 最大,就可以解决此问题。

但如果依次考虑所有包含点的列,则最坏情况下时间复杂度为 O(N2)\mathcal O(N^2),无法通过此题。这时,我们可以使用一个 multisetmap 来维护当前每列对答案的贡献(csCAR,C\mathrm{cs}_C-A_{R,C})。对于每一行 RR,仅需更新这一行上有非 00 数字的点 (R,C)(R,C) 的贡献(减去 AR,CA_{R,C})即可。

这样,由于每个点会被更新正好一次,所以总时间复杂度为 O(NlogN)\mathcal O(N\log N)

代码

注意更新完成,求得当前答案后需要复原 mapmultiset

#include <cstdio>
#include <vector>
#include <set>
#include <unordered_map>
using namespace std;

using LL = long long;
using pii = pair<int, int>;

unordered_map<int, vector<pii>> rows;
unordered_map<int, LL> col_sum;

template <typename T>
class MaxSet {
private:
	multiset<T> s;
public:
	inline void insert(const T& x) { s.insert(x); }
	inline void update(const T& old, const T& New) {
		s.erase(s.find(old));
		s.insert(New);
	}
	inline T max() { return *s.rbegin(); }
};

int main()
{
	int n;
	scanf("%d", &n);
	while(n--)
	{
		int x, y, v;
		scanf("%d%d%d", &x, &y, &v);
		rows[x].emplace_back(y, v);
		col_sum[y] += v;
	}
	MaxSet<LL> s;
	for(auto [_, sum]: col_sum)
		s.insert(sum);
	LL ans = 0LL;
	for(auto& [x, v]: rows)
	{
		for(auto [y, val]: v)
			s.update(col_sum[y], col_sum[y] - val);
		LL cur = s.max();
		for(auto [y, val]: v)
			s.update(col_sum[y] - val, col_sum[y]), cur += val;
		if(cur > ans) ans = cur;
	}
	printf("%lld\n", ans);
	return 0;
}

G - Strawberry War

题目大意

注意本题时间限制为 6s6\mathrm s

我们有一块长方形的蛋糕。它可被看作一个 H×WH\times W 的网格,第 iijj 列上有 si,js_{i,j} 个草莓。

我们对蛋糕进行 TT 次切分,切成 T+1T+1 块。每次切蛋糕可以选择当前的一块,并将其从中间横切或竖切成两块:
切分方式

你想把蛋糕切的尽可能均匀。意思是,令 MM 表示切分完成后每一块上的草莓数量的最大值,mm 表示最小值,求出 MmM-m 的最小值。

1H,W61\le H,W\le 6
1THW11\le T\le HW-1
0si,j10160\le s_{i,j}\le 10^{16}

分析

本题解参考官方题解

操作完成后得到的蛋糕一定是蛋糕的子矩形,所以最多只有 (H+12)×(W+12)=H(H+1)W(W+1)4441\binom {H+1}2\times\binom {W+1}2=\frac{H(H+1)W(W+1)}4\le 441 种数字在剩下的块中。令这些可能的数分别为 a1,a2,,aXa_1,a_2,\dots,a_X。可知 XH(H+1)W(W+1)4441X\le \frac{H(H+1)W(W+1)}4\le 441

根据上面的 aa,我们只需先确定 m{a1,a2,,aX}m\in \{a_1,a_2,\dots,a_X\},再找到与其对应的最小 MM,算出 MmM-m 的最小值即可。

定义 fi,j,k,l,mf_{i,j,k,l,m} 表示将 x[i,j),y[k,l)x\in[i,j),y\in[k,l) 的子矩形切成 mm 片时最小可能的每片上草莓数的最大值。

详见代码。

代码

#include <bits/stdc++.h>
using namespace std;
const long long INF = 1000000000000000000;
int main(){
  int H, W, T;
  cin >> H >> W >> T;
  vector<vector<long long>> s(H, vector<long long>(W));
  for (int i = 0; i < H; i++){
    for (int j = 0; j < W; j++){
      cin >> s[i][j];
    }
  }
  vector<vector<vector<vector<long long>>>> sum(H, vector<vector<vector<long long>>>(H + 1, vector<vector<long long>>(W, vector<long long>(W + 1, 0))));
  vector<long long> x;
  for (int i = 0; i < H; i++){
    for (int j = i + 1; j <= H; j++){
      for (int k = 0; k < W; k++){
        for (int l = k + 1; l <= W; l++){
          for (int m = i; m < j; m++){
            for (int n = k; n < l; n++){
              sum[i][j][k][l] += s[m][n];
            }
          }
          x.push_back(sum[i][j][k][l]);
        }
      }
    }
  }
  int cnt = x.size();
  long long ans = INF;
  for (int i = 0; i < cnt; i++){
    vector<vector<vector<vector<vector<long long>>>>> dp(T + 1, vector<vector<vector<vector<long long>>>>(H, vector<vector<vector<long long>>>(H + 1, vector<vector<long long>>(W, vector<long long>(W + 1, INF)))));
    for (int j = H - 1; j >= 0; j--){
      for (int k = j + 1; k <= H; k++){
        for (int l = W - 1; l >= 0; l--){
          for (int m = l + 1; m <= W; m++){
            if (sum[j][k][l][m] >= x[i]){
              dp[0][j][k][l][m] = sum[j][k][l][m];
            }
            for (int n = j + 1; n < k; n++){
              for (int o = 0; o < (n - j) * (m - l); o++){
                for (int p = 0; p < (k - n) * (m - l) && o + p < T; p++){
                  dp[o + p + 1][j][k][l][m] = min(dp[o + p + 1][j][k][l][m], max(dp[o][j][n][l][m], dp[p][n][k][l][m]));
                }
              }
            }
            for (int n = l + 1; n < m; n++){
              for (int o = 0; o < (k - j) * (n - l); o++){
                for (int p = 0; p < (k - j) * (m - n) && o + p < T; p++){
                  dp[o + p + 1][j][k][l][m] = min(dp[o + p + 1][j][k][l][m], max(dp[o][j][k][l][n], dp[p][j][k][n][m]));
                }
              }
            }
          }
        }
      }
    }
    ans = min(ans, dp[T][0][H][0][W] - x[i]);
  }
  cout << ans << endl;
}