AtCoder Beginner Contest 224

本文介绍了多种字符串处理和算法题目,包括简单的字符串判断、矩阵遍历验证、三角形计数、8-puzzle问题的最短步数、动态规划解决数列问题以及概率论中的最优策略计算。每个题目都提供了相应的解决方案,涉及暴力枚举、动态规划、数学公式推导等方法。

A. 简单判断

题意:给定一个字符串,判断是以 ererer 结尾还是以 ististist 结尾

判断最后一个字母是 rrr 还是 ttt 即可

#include<bits/stdc++.h>
using namespace std;

string s;

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> s;
	if (s.back() == 'r') cout << "er\n";
	else cout << "ist\n";
	return 0;
}

B. 暴力枚举

题意:给定一个 H×WH\times WH×W 的矩阵,判断对于所有的 1≤i1≤i2≤H,1≤j1≤j2≤W1\leq i_1\leq i_2\leq H, 1\leq j_1\leq j_2\leq W1i1i2H,1j1j2W,是否满足 Ai1,j1+Ai2,j2≤Ai2,j1+Ai1,j2A_{i_1, j_1} + A_{i_2, j_2}\leq A_{i_2, j_1} + A_{i_1, j_2}Ai1,j1+Ai2,j2Ai2,j1+Ai1,j2

暴力枚举所有的 i1,i2,j1,j2i_1, i_2, j_1, j_2i1,i2,j1,j2 即可,时间复杂度:O(H2W2)O(H^2W^2)O(H2W2)

#include<bits/stdc++.h>
using namespace std;

int n, m;
const int N = 55;
int a[N][N];

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> a[i][j];
		}
	}
	for (int i1 = 1; i1 <= n; i1++) {
		for (int i2 = i1 + 1; i2 <= n; i2++) {
			for (int j1 = 1; j1 <= m; j1++) {
				for (int j2 = j1 + 1; j2 <= m; j2++) {
					if (a[i1][j1] + a[i2][j2] > a[i2][j1] + a[i1][j2]) {
						cout << "No\n";
						return 0;
					}
				}
			}
		}
	}
	cout << "Yes\n";
	return 0;
}

C. 暴力枚举

题意:给定 NNN 个不重复的点,判断能组成多少个三角形

因为 N≤300N\leq 300N300 这一条件,所以我们可以直接暴力 O(N3)O(N^3)O(N3) 枚举所有的点组合,并判断每个点组合是否可行即可

判断 (xi,yi),(xj,yj),(xk,yk)(x_i, y_i), (x_j, y_j), (x_k, y_k)(xi,yi),(xj,yj),(xk,yk) 是否可行,即判断其是否共线

  • d1=(xj−xi,yj−yi),d2=(xk−xi,yk−yi)d_1 = (x_j - x_i, y_j - y_i), d_2 = (x_k - x_i, y_k - y_i)d1=(xjxi,yjyi),d2=(xkxi,ykyi)
  • 如果 d1,d2d_1, d_2d1,d2 共线,那么 d1×d2=(xj−xi)(yk−yi)−(yj−yi)(xk−xi)=0d_1 \times d_2 = (x_j - x_i)(y_k - y_i) - (y_j - y_i)(x_k - x_i) = 0d1×d2=(xjxi)(ykyi)(yjyi)(xkxi)=0,注意这里是向量叉积,我们只需要判断上式是否成立即可
  • 反之不共线
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 3e2 + 5;
int n, x[N], y[N];

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> x[i] >> y[i];
	}
	ll cnt = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = i + 1; j <= n; j++) {
			for (int k = j + 1; k <= n; k++) {
				int dx1 = x[j] - x[i], dy1 = y[j] - y[i];
				int dx2 = x[k] - x[i], dy2 = y[k] - y[i];
				cnt += 1ll * dx1 * dy2 - 1ll * dx2 * dy1 != 0;
			}
		}
	}
	cout << cnt << '\n';
	return 0;
}

D. 暴力模拟

题意:8-puzzle 问题的变种,求将 111888 编号的棋子放到 111999 编号的节点上的最小步数,同一时间每个节点最多只能放 111 个棋子,节点之间存在转移边

总状态数为 9!9!9!,转移数最大为 999,所以暴力模拟的复杂度为 O(9×9!)O(9\times 9!)O(9×9!),需要记录当前状态是否被走过

因为需要求的是最小操作次数,所以需要 bfsbfsbfs 而不能用 dfsdfsdfs,具体细节见代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 55;
int m, vis[N];
vector<int> nxt[N];
map<vector<int>, int> d;
vector<int> tar = {0, 1, 2, 3, 4, 5, 6, 7, 8};

void bfs(vector<int> p) {
	queue<vector<int>> quu;
	quu.push(p);
	d[p] = 0;
	while (!quu.empty()) {
		vector<int> now = quu.front(); quu.pop();
		if (now == tar) {
			cout << d[now] << '\n';
			return;
		}
		fill(vis, vis + 10, 0);
		for (int i = 1; i <= 8; i++) {
			vis[now[i]] = 1;
		}
		for (int i = 1; i <= 8; i++) {
			int pos = now[i], step = d[now];
			for (auto it: nxt[pos]) {
				if (vis[it]) continue;
				vis[pos] = 0;
				vis[it] = 1;
				now[i] = it;
				if (!d.count(now)) {
					d[now] = step + 1;
					quu.push(now);
				}
				now[i] = pos;
				vis[pos] = 1;
				vis[it] = 0;
			}
		}
	}
	cout << -1 << '\n';
}

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> m;
	for (int i = 1, u, v; i <= m; i++) {
		cin >> u >> v;
		nxt[u].push_back(v);
		nxt[v].push_back(u);
	}
	vector<int> p(9, 0);
	for (int i = 1; i <= 8; i++) {
		cin >> p[i];
	}
	bfs(p);
	return 0;
}

E. 动态规划

题意:在一个方格上走,每次只能走同一行或同一列,且到达的格点上的值必须严格大于当前值,问最多走多少步

我们可以按行和列进行 dpdpdp

row[i]row[i]row[i] 表示当前第 iii 行的数向后最多能走多少步,col[i]col[i]col[i] 表示当前第 iii 列的数向后最多能走多少步

可以将数从大向小加入方格中,维护 row[i]row[i]row[i]col[i]col[i]col[i] 的变化

对于即将加入个点的数 (r,c,a)(r, c, a)(r,c,a),他的贡献是 row[r]←row[r]+1,col[c]←col[c]+1row[r] \leftarrow row[r] + 1, col[c]\leftarrow col[c] + 1row[r]row[r]+1,col[c]col[c]+1

需要注意的是,移动到的点必须是值严格大于他的,所以不能一个数一个数去加,而应该对于具有相同值的数一起加,再统一更新

时间复杂度:O(Nlog⁡N)O(N\log N)O(NlogN)

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5 + 5;
int h, w, n, row[N], col[N], r[N], c[N], a[N], ans[N];
map<int, vector<int>> mp;

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> h >> w >> n;
	for (int i = 1; i <= n; i++) {
		cin >> r[i] >> c[i] >> a[i];
		mp[-a[i]].push_back(i);
	}
	for (auto it: mp) {
		vector<int>& now = it.second;
		for (auto id: now) {
			ans[id] = max(row[r[id]], col[c[id]]);
		}
		for (auto id: now) {
			row[r[id]] = max(row[r[id]], ans[id] + 1);
			col[c[id]] = max(col[c[id]], ans[id] + 1);
		}
	}
	for (int i = 1; i <= n; i++) {
		cout << ans[i] << '\n';
	}
	return 0;
}

F. 数学公式推导

题意:给定一个数字串,向其中随意放入加号组成表达式,问所有情况的表达式值的和是多少

很显然这是一个推公式的题

假如当前是第 iii 位,当前数是第 jjj 位,这样的情况数有多少种呢

  • i+j,i+j+1,...,Ni + j, i + j + 1, ... , Ni+j,i+j+1,...,N 随意分配加号,情况数为 2N−i−j2^{N - i - j}2Nij 种,需要注意的是 i+j−1=Ni + j - 1 = Ni+j1=N 时情况数为 111,这种情况分开讨论
  • 1,2,...,i−11, 2, ..., i - 11,2,...,i1 随意分配加号,i−1i - 1i1 后面可分配可不分配,一共 2i−12^{i - 1}2i1 种情况

那么,答案表达式为:
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ ans &= \sum\li…
对于 ∑i=1Nci×(2i−1×10N−i)\sum\limits_{i = 1}^{N}c_i \times(2^{i - 1}\times 10^{N - i})i=1Nci×(2i1×10Ni) 可以 O(N)O(N)O(N)

对于 ∑i=1Nci×∑j=1N−i2N−j−1×10j−1\sum\limits_{i = 1}^{N}c_i\times \sum\limits_{j = 1}^{N - i}2^{N - j - 1}\times 10^{j - 1}i=1Nci×j=1Ni2Nj1×10j1,其实不难发现 ∑j=1N−i2N−j−1×10j−1\sum\limits_{j = 1}^{N - i}2^{N - j - 1}\times 10^{j - 1}j=1Ni2Nj1×10j1 是一个表达式内与 iii 无关的量,我们只需要预处理出 ∑j=1q2N−j−1×10j−1\sum\limits_{j = 1}^{q}2^{N - j - 1}\times 10^{j - 1}j=1q2Nj1×10j1 的表即可,后续就可以直接 O(1)O(1)O(1) 查了,所以复杂度也是 O(N)O(N)O(N)

总时间复杂度:O(N)O(N)O(N)

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5 + 5, mod = 998244353;
char s[N];
int n, ans, pw2[N], pw10[N], v[N];

int main(void) {
	scanf("%s", s + 1);
	n = strlen(s + 1);
	pw2[0] = pw10[0] = 1;
	for (int i = 1; i <= n; i++) {
		pw2[i] = 1ll * pw2[i - 1] * 2 % mod;
		pw10[i] = 1ll * pw10[i - 1] * 10 % mod;	
	}
	for (int i = 1; i < n; i++) {
		v[i] += (v[i - 1] + 1ll * pw2[n - i - 1] * pw10[i - 1]) % mod;
	}
	for (int i = 1; i <= n; i++) {
		int c = s[i] - '0';
		int tmp = 0;
		for (int j = 1; j <= n - i; j++) {
			tmp += 1ll * pw2[n - j - 1] % mod * pw10[j - 1] % mod;
			tmp %= mod;
		}
		ans += 1ll * c * v[n - i] % mod; ans %= mod;
		ans += 1ll * pw2[2, i - 1] * c % mod * pw10[10, n - i] % mod; ans %= mod;
	}
	cout << ans << '\n';
	return 0;
}

G. 概率论

题意:给定起点 SSS 与终点 TTT,可以做两个操作

  • S←S+1S\leftarrow S+1SS+1,代价为 AAA
  • SSS 随机变为 [1,N][1, N][1,N] 的随机一个整数,代价为 BBB

问在最优策略下,从 SSS 走到 TTT 的期望代价最小是多少

关键在于选什么样的策略

有两种可能的情况:

  • 先随机走,如果随机到一个比较好的值,就一直加到 TTT
  • 如果满足 S≤TS\leq TST,的话可以直接加到 TTT

不难发现,这两种做法实际上是完全独立的,也就是说不可能出现先加再随机的情况(这肯定不好),在一开始就要选定是用两种策略里的哪一种

事实上,我们只需要计算两种策略的最小值就是答案

对于第二种情况很简单,就是 (T−S)×A(T - S)\times A(TS)×A

对于第一种情况,一定是有一个阈值 x,(x≤T)x,(x\leq T)x,(xT),如果随机到的数满足 [x,T][x, T][x,T],就一直加到 TTT,反之继续随机

随机到 [x,T][x, T][x,T] 的期望步数为 NT−x+1\frac{N}{T - x + 1}Tx+1N,期望值为 NT−x+1B\frac{N}{T - x + 1}BTx+1NB

对已随机到 [x,T][x, T][x,T] 的所有情况,其到达 TTT 的数学期望为 0+1+...+T−xT−x+1=T−x2\frac{0 + 1 + ... + T - x}{T - x + 1} = \frac{T - x}{2}Tx+10+1+...+Tx=2Tx,期望值为 T−x2A\frac{T - x}{2}A2TxA

总期望为 NT−x+1B+T−x2A\frac{N}{T -x + 1}B + \frac{T - x}{2}ATx+1NB+2TxA,我们需要选定一个合适的 xxx,来使这个表达式最小

不难发现上述表达是是一个钩型函数 x+axx + \frac{a}{x}x+xa 的形式,所以我们可以算出其解析解,或者三分求最小值

将表达式的最小值与情况 222 的值取最小值即为答案

时间复杂度:O(1)O(1)O(1)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll

int n, s, t, a, b;

double f(int x) {
	return 1.0 * a / 2 * (t - x) + 1.0 * n * b / (t - x + 1);
}

double bin3(void) {
	int l = 1, r = t;
	double ret = min(f(l), f(r));
	while (l <= r) {
		int m = (l + r) / 2, mm = (m + r) / 2;
		double fm = f(m), fmm = f(mm);
		if (fm >= fmm) l = m + 1;
		else r = mm - 1;
		ret = min(ret, min(fm, fmm));
	}
	return ret;
}

signed main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> s >> t >> a >> b;
	cout.precision(50);
	double ans = bin3();
	if (s <= t) ans = min(ans, 1.0 * (t - s) * a);
	cout << ans << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值