AtCoder Beginner Contest(abc) 311

发布时间 2023-10-31 09:13:44作者: mostimali




B - Vacation Together

难度: ⭐

题目大意

给定n个人的工作计划, 'o'表示这天休息, 'x'表示工作; 请找出一段最长的所有人都休息的连续休息的天数;

解题思路

数据不大, 暴力即可;

神秘代码

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 1e3 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m;
int f[N];
signed main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			char c;
			cin >> c;
			if (c == 'o') f[j]++;
		}
	}
	int res = 0, maxn = 0;
	for (int i = 1; i <= m; i++) {
		if (f[i] == n) res++;
		else {
			maxn = max(maxn, res);
			res = 0;
		}
	}
	maxn = max(maxn, res);
	cout << maxn;
	return 0;
}




C - Find it!

难度: ⭐⭐

题目大意

给定一个有向图, 不一定连通, 但是保证一定存在环, 请你找出任意一个环, 该环中没有重复的节点, 输出环中结点的数量和所有结点;

解题思路

用一个数组保存路径, dfs遍历所有点即可;

神秘代码

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 2e5 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m;
vector<int> v[N];
vector<int> res;
int d[N];
bool st[N];
void find(int u) {
	if (st[u]) {
		int i = 0;
		while (res[i] != u) i++;
		cout << res.size() - i-1 << endl;
		for (; i < res.size()-1; i++) cout << res[i] << ' ';
		exit(0);
	}
	st[u] = true;
	for (int x : v[u]) {
		res.push_back(x);
		find(x);
		res.pop_back();
	}
}
signed main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int x;
		cin >> x;
		v[i].push_back(x);
	}
	for (int i = 1; i <= n; i++) {
		if (!st[i]) {
			res.push_back(i);
			find(i);
			res.pop_back();
		}
	}
	return 0;
}




D - Grid Ice Floor

难度: ⭐⭐⭐

题目大意

给定一个n*m的字符矩阵, 其中'.'表示这块是雪地, '#'表示是岩石; 该矩阵的最外圈都是岩石; 小莫的初识位置在(2,2)这个点; 小莫接下来可以选择上下左右四个方向, 确定好方向后就会一直沿着这个方向前进直到遇到岩石才会停下来再选择方向; 问小莫可以经过多少块雪地;

解题思路

这题dfs和bfs好像都可以, 但是bfs会更好写; 在理解题意后我们可以想到, 小莫可能以不同的方向经过同一块雪地, 所以在保存状态时我们要三个变量, 坐标x, y和方向d; 所以我们要对经过的地方打上两个标记, sw(是否经过某个坐标), st(以某个方向经过某个坐标); 再更新队列时, 如果还能按照原方向接着走, 就接着更新即可; 如果不行就遍历4个方向找可以走的方向, 只要下一个不越界, 并且以该方向没有经历过就可以放进队列中;

神秘代码

#include<bits/stdc++.h>
#include<unordered_map>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 1e3 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m, res;
char g[N][N];
bool sw[N][N];
bool st[N][N][5];
struct stu {
	int x, y, d;
};
int dx[] = { 1,0,-1,0 }, dy[] = { 0,1,0,-1 };
void bfs(){
	queue<stu> q;
	for (int i = 0; i < 4; i++) {
		q.push({ 2,2,i });
		st[2][2][i] = 1;
	}
	while (q.size()) {
		auto t = q.front();
		if (!sw[t.x][t.y]) {
			res++;
			sw[t.x][t.y] = true;
		}		
		q.pop();
		int xf = t.x + dx[t.d], yf = t.y + dy[t.d], d = t.d;
		if (xf>=1&&xf<=n&&yf>=1&&yf<=m&&g[xf][yf] == '.') {
			if (st[xf][yf][d]) continue;
			q.push({ xf,yf,d });
			st[xf][yf][d] = 1;
		}
		else {
			for (int i = 0; i < 4; i++) {
				int x = t.x + dx[i], y = t.y + dy[i];
				if (x >= 1 && x <= n && y >= 1 && y <= m && g[x][y] == '.') {
					if (st[x][y][i]) continue;
					q.push({ x,y,i });
					st[x][y][i] = 1;
				}
			}
		}
	}
}
signed main(){
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> g[i][j];
		}
	}
	bfs();
	cout << res;
	return 0;
}




E - Defect-free Squares

难度: ⭐⭐⭐

题目大意

在一张n*m的图中, 有k个坐标格子上有个洞; 请问有多少个正方形区域内没有洞; 只要两个方形区域的左上方坐标和右下方坐标和其他方形区域不同, 那么这两个方形区域就可以看作是不同的; 单独的一个格子也可以看作一个边长为1的方形区域;

解题思路

由题意我们可以想到先固定一个不是洞的坐标(x,y), 然后求出所有以该坐标为右下角坐标的方形区域个数; 并且我们发现只要存在以(x-1,y), (x-1,y-1), (x,y-1)这三个相邻位置坐标为右下角坐标的方形区域, 我们就可以用他们来推断以(x,y)为右下角坐标的方形区域中是否有洞; 只要三个坐标都存在边长为n的方形区域, 我们就可以(x,y)为右下角坐标且边长为(n+1)的方形区域中没有洞; 像这种存在相互关系的计数问题, 我们可以考虑用dp来做; 具体方式也很直接, 状态表示dp[i][j]表示以(i,j)为右下角的没有洞的方形区域个数; 状态计算就像刚才说的: dp[i][j] = min(dp[i - 1][j - 1], min(dp[i][j - 1], dp[i - 1][j])) + 1; 最后遍历所有坐标把dp[i][j]加起来即可;

神秘代码

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 1e5 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m, res;
bool st[3010][3010];
int dp[3010][3010];
signed main(){
	IOS;
	int k;
	cin >> n >> m >> k;
	for (int i = 1; i <= k; i++) {
		int a, b;
		cin >> a >> b;
		st[a][b] = true;
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (st[i][j]) continue;
			dp[i][j] = min(dp[i - 1][j - 1], min(dp[i][j - 1], dp[i - 1][j])) + 1;
		}
	}
	int res = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			res += dp[i][j];
		}
	}
	cout << res;
	return 0;
}




F - Yet Another Grid Task

难度: ⭐⭐⭐⭐

题目大意

给定一个由黑白色块表示的n*m的一个图; '.'表示白色, '#'表示黑色; 如果图中的所有黑色块都满足下面这个条件, 那么这个图就是合法的;
条件是: 设一个黑色块的坐标为(x,y): 如果坐标(x+1,y)存在, 那么这个坐标的色块必须是黑色; 如果坐标(x+1,y+1)存在, 那么这个坐标的色块也必须是黑色;
现在我们可以选择把任意数量的白色块染成黑色块, 请问我们可以把原图构造为多少种不同的合法的图;

解题思路

由题意得, 假设当前是一个合法图, 如果在第i列有一个自下而上高度为j的黑色块, 那么第i列从下往上一直到j都要是黑色块, 同时, 第i+1列必须要有一个高度为j-1的黑色块; 再继续分析, 还是第i列有一个高度为j的黑色块, 那么第i-1列的黑色块高度最高就是j+1, 如果再往上, 那么第i列高度为j的色块就不合法了; 发现有状态的转移, 所以可以考虑用dp来求解; 一开始我们先按题目要求把原图修改为最基础的合法图, 然后我们在这个图上进行修改; 状态表示: dp[i][j] 表示修改前i列, 并且第i列的黑色块高度最高为j时所有合法图的数量; 根据前面所提到的范围, 我们可以得到状态转移: dp[i][j] = dp[i][j] + dp[i - 1][k], 但是这样需要三重循环, 所以我们可以先从第i列的j-1行开始转移; 因为dp[i][j-1]已经包括了第i-1列高度一直到(j-1+1) = j的情况; 所以dp[i][j]只需要再补充一个到j+1的高度即可, 如果j+1>n了就再补充一个n即可; 所以新的转移方差就是dp[i][j] = dp[i][j-1] + dp[i - 1][min(n,j+1)];

神秘代码

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 2e3 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m, res;
char g[N][N];
int dp[N][N];
int bak[N];
signed main(){
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> g[i][j];
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (g[i][j] == '#'){
				g[i + 1][j] = g[i + 1][j + 1] = '#';
				bak[j] = max(bak[j], n - i + 1);
			}
		}
	}
	for (int i = 0; i <= n; i++) dp[0][i] = 1;

    // for (int i = 1; i <= m; i++) {
	// 	for (int j = bak[i]; j <= n; j++) {
	// 		for (int k = bak[i]; k <= min(n,j + 1); k++) {
	// 			(dp[i][j] += dp[i - 1][k]) %= mod;
	// 		}
	// 	}
	// }这是朴素版本, n的三次方

	for (int i = 1; i <= m; i++) {
		for (int j = bak[i]; j <= n; j++) {
			(dp[i][j] = dp[i][j-1] + dp[i - 1][min(n,j+1)]) %= mod;//前缀和优化
		}
	}
	cout << dp[m][n];
	return 0;
}