AtCoder Beginner Contest 224

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

A. 简单判断

题意:给定一个字符串,判断是以 e r er er 结尾还是以 i s t ist ist 结尾

判断最后一个字母是 r r r 还是 t t t 即可

#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 × W H\times W H×W 的矩阵,判断对于所有的 1 ≤ i 1 ≤ i 2 ≤ H , 1 ≤ j 1 ≤ j 2 ≤ W 1\leq i_1\leq i_2\leq H, 1\leq j_1\leq j_2\leq W 1i1i2H,1j1j2W,是否满足 A i 1 , j 1 + A i 2 , j 2 ≤ A i 2 , j 1 + A i 1 , j 2 A_{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

暴力枚举所有的 i 1 , i 2 , j 1 , j 2 i_1, i_2, j_1, j_2 i1,i2,j1,j2 即可,时间复杂度: O ( H 2 W 2 ) 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. 暴力枚举

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

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

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

  • d 1 = ( x j − x i , y j − y i ) , d 2 = ( x k − x i , y k − y i ) 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)
  • 如果 d 1 , d 2 d_1, d_2 d1,d2 共线,那么 d 1 × d 2 = ( x j − x i ) ( y k − y i ) − ( y j − y i ) ( x k − x i ) = 0 d_1 \times d_2 = (x_j - x_i)(y_k - y_i) - (y_j - y_i)(x_k - x_i) = 0 d1×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 问题的变种,求将 1 1 1 8 8 8 编号的棋子放到 1 1 1 9 9 9 编号的节点上的最小步数,同一时间每个节点最多只能放 1 1 1 个棋子,节点之间存在转移边

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

因为需要求的是最小操作次数,所以需要 b f s bfs bfs 而不能用 d f s dfs dfs,具体细节见代码

#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. 动态规划

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

我们可以按行和列进行 d p dp dp

r o w [ i ] row[i] row[i] 表示当前第 i i i 行的数向后最多能走多少步, c o l [ i ] col[i] col[i] 表示当前第 i i i 列的数向后最多能走多少步

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

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

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

时间复杂度: O ( N log ⁡ 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. 数学公式推导

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

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

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

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

那么,答案表达式为:
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ ans &= \sum\li…
对于 ∑ i = 1 N c i × ( 2 i − 1 × 1 0 N − 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 = 1 N c i × ∑ j = 1 N − i 2 N − j − 1 × 1 0 j − 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 = 1 N − i 2 N − j − 1 × 1 0 j − 1 \sum\limits_{j = 1}^{N - i}2^{N - j - 1}\times 10^{j - 1} j=1Ni2Nj1×10j1 是一个表达式内与 i i i 无关的量,我们只需要预处理出 ∑ j = 1 q 2 N − j − 1 × 1 0 j − 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. 概率论

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

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

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

关键在于选什么样的策略

有两种可能的情况:

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

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

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

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

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

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

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

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

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

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

时间复杂度: 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值