Processing math: 0%

AtCoder Beginner Contest 243

AtCoder Beginner Contest 243 Solution

A - Shampoo

题意

牙膏的体积为 VF M T 三人每天需要用的牙膏量为 A,B,C,并且每天使用牙膏的顺序都是F M T,输出若干天后,哪个人会首先没有充足的牙膏用。

数据范围 : 1 \leq V,A,B,C \leq 10^5

题解

考虑直接模拟牙膏每天的使用情况,复杂度O(\frac{V}{A+B+C})

C++代码示例

# include <bits/stdc++.h>
using namespace std;
int main() {
	int v,a,b,c; cin>>v>>a>>b>>c;
	int now = 0;
	while (v >= 0) {
		if (now == 0) {
			v-=a; if (v < 0) puts("F");
		}else if (now == 1) {
			v-=b; if (v < 0) puts("M");
		}else if (now == 2) {
			v-=c; if (v < 0) puts("T");
		}
		now=(now+1)%3;
	}
	return 0;
}

B - Hit and Blow

题意

有长度为 n 的两个数组 a,b ,输出两个数:

  • 1 个数:有多少个 i(1 \leq i \leq n) 满足 a_i = b_i
  • 2 个数:有多少对(i,j) (i \ne j, 1 \leq i,j \leq n) 满足 a_i = b_j

数据范围:1 \leq n \leq 10^3, 1 \leq a_i,b_i \leq 10^9,满足 a,b 数组内部元素互不相同。

题解

对于第 1 个数,可以用 O(n) 的线性遍历实现。

对于第 2 个数,显然有一个 O(n^2) 的暴力做法,直接模拟即可。

\text{Bonus} : 对于第 2 个数,有一个 O(n \log_2 n) 的做法。

用 set 将 a 中的元素存入,遍历 b 中的所有元素,如果发现 a_i = b_i 则累加 ans_1,否则看 set 中是否有一个值为 b_i 的元素 ,如果有则累加 ans_2

本做法如果将 set 改为 map,可以处理“取消限制 a,b 数组内部元素互不相同”的情况。

时间复杂度 O(n \log_2 n)

C++ 代码示例

# include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int a[N], b[N];
int main() {
	int n; cin >> n;
	for (int i = 1 ; i <= n ; i++) {
		cin >> a[i];
	}
	set<int>s;
	for (int i = 1 ; i <= n ; i++) {
		cin >> b[i];
		s.insert(b[i]);
	}
	int ans1 = 0, ans2 = 0;
	for (int i = 1; i <= n; i++) {
		if (a[i] == b[i]) ans1++;
		else if (s.find(a[i]) != s.end()) ans2++;
	}
	cout << ans1 << endl <<ans2 << endl;
	return 0;
}

C - Collision 2

题意

n 个人,第 i(1 \leq i \leq n) 个人的位置在 (x_i,y_i) 处。每个人有一个面朝方向,没有两个人在同一位置。

这个方向由字符串 s 给出,s_i = L 表示面朝水平左,否则面朝水平右。

如果这 n 个人保持面朝的方向匀速走,会发生碰撞输出 Yes 否则输出 No

数据范围:2 \leq n \leq 2\times 10^5 , 0 \leq x_i,y_i \leq 10^9, |s| = n

题解

首先,可以发现不在同一条水平线上的人不会相互影响。

因此,可以用一个map<int, vector<int>>将不同 y 值的人,分成不同的组。

问题转化为在一条水平线上,有一些人,坐标互不相同,面朝左右,相同速度移动是否相碰。

那么将这些人按照 x 坐标排序。

容易发现,如果在排序后的人所面朝的L/R序列中有两相邻的字符是RL,那么必然会相碰。

并且这个条件是一个充分必要条件。

时间复杂度为 O(n \log_2 n)

C++代码示例

# include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
struct rec{
	int x, y;
}a[N];
string s;
map<int, vector<pair<int, int>>>v;
int main() {
	int n; cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> a[i].x >> a[i].y;
		v[a[i].y].push_back({a[i].x, i});
	}
	cin >> s;
	bool ans = false;
	for (auto x : v) {
		vector<pair<int, int>>tmp = x.second;
		sort(tmp.begin(),tmp.end());
		for (int i = 0; i < tmp.size()-1; i++) 
			if (s[tmp[i].second] == 'R' && s[tmp[i+1].second] == 'L') ans = true;
	}
	if (ans) puts("Yes"); else puts("No");
	return 0;
}

D - Moves on Binary Tree

题意

有一棵完全二叉树,节点编号从 1 开始,从上到下从左到右对节点依次编号。

从编号为 x 的节点开始走,有一个长度为 n 的序列 s,描述走的每一步。

  • s_iU ,表示走到当前节点的父亲节点。
  • s_iL ,表示走到当前节点的左儿子节点。
  • s_iR ,表示走到当前节点的右儿子节点。

请输出最终走到的节点标号,保证答案在[1,10^{18}]

数据范围: 1 \leq n \leq 10^6, 1 \leq x \leq 10^{18}

题解

显然,对应的操作序列是 x \rightarrow \{\lfloor \frac{x}{2} \rfloor, 2x,2x+1\}

这可以类比成:

  • x \rightarrow \lfloor \frac{x}{2}\rfloor : 将二进制数 x 的末尾的 0 或者 1 舍去。
  • x \rightarrow 2x : 将二进制数 x 的末尾添加一个 0
  • x \rightarrow 2x + 1 : 将二进制数 x 的末尾添加一个 1

这可以使用双端队列进行维护。

时间复杂度 O(n)

# include <bits/stdc++.h>
# define int long long
using namespace std;
signed main() {
	int n, x; cin >> n >> x;
	string s; cin >> s;
	deque<int>q;
	while (x) {
		q.push_front(x&1);
		x >>= 1;
	}
	for (char t : s) {
		if (t == 'U') q.pop_back();
		else if (t == 'L') q.push_back(0);
		else if (t == 'R') q.push_back(1);
	}
	int ans = 0, pw = 0;
	while (q.size()) {
		ans += q.back() * (1ll << pw);
		pw++; q.pop_back();
	}
	cout<< ans << endl;
	return 0;
}

E - Edge Deletion

题意

n 个点 m 条边的带边权连通无向图,第 i (1 \leq i \leq m) 条无向边是(a_i,b_i,c_i),表示 a_ib_i 间有长度为 c_i 的无向边。输出最多可以删除的边数,使得图中任意两点最短距离不变。

数据范围: 2 \leq n \leq 300 , n-1 \leq m \leq \frac{n(n-1)}{2}, 1 \leq c_i \leq 10^9

题解

首先可以用 \text{floyd} 算法求出两点间的最短距离 d[i][j]

接下来依次考虑每一条边是否可以被删去,设该边为(u,v,w)

可以删去的条件是存在一个点 i (1 \leq i \leq n ,i \ne u,i \ne v) 满足 d[u][i] + d[i][v] \leq w

时间复杂度为 O(n^3 + nm)

C++ 代码示例

# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N = 3e2 + 10;
int a[N*N], b[N*N], c[N*N], d[N][N];
signed main() {
	int n, m;  cin >> n >> m;
	memset(d,0x3f,sizeof(d));
    for (int i = 1; i <= n; i++) d[i][i] = 0;
	for (int i = 1; i <= m; i++) {
		cin >> a[i] >> b[i] >> c[i];
		d[a[i]][b[i]] = d[b[i]][a[i]] = c[i];
	}
	for (int k = 1; k <= n; k++)
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n ; j++)
				d[i][j] = min(d[i][j],d[i][k]+d[k][j]);	
	int ans = 0;
	for (int i = 1; i <= m; i++) {
		int u = a[i], v = b[i], w = c[i];
		for (int j = 1; j <= n; j++)
			if (w >= d[u][j] + d[j][v] && u != j && v != j) {
				ans++; break;
			}
	}
	cout << ans << endl;
	return 0;
}

F - Lottery

题意

n 种彩票,每种彩票有一个权值w_i,表示其中奖率为\frac{w_i}{\sum_{i = 1} ^{n}w_i}

求买 m 张彩票,恰好中了 k 种彩票的奖的概率。

答案对 998244353 取模。

数据范围:1 \leq k \leq 50, 1 \leq m \leq n \leq 50, 0 < w_i \leq 998244353 , 0 \leq \sum_{i = 1} ^{n} w_i \leq 998244353

题解

假设每种彩票分别买了c_i (1 \leq i \leq n) 张,第 i 种彩票中奖概率为p_i

中奖的概率可以由超几何分布求出:p = \frac{k!}{\prod_{i=1}^{n} c_i!}\prod_{i = 1} ^ {n} p_i ^ {c_i} = k!\frac{\prod_{i = 1} ^ {n} p_i ^ {c_i}}{\prod_{i=1}^{n} c_i!}

f[i][j][k] 表示前 i 种彩票,一共中奖了j 种,一共买了k张的概率。显然 f[0][0][0] = 1

转移考虑第 i+1 种彩票买了 x 张,f[i+1][j+(x\ne 0)][k + x] += f[i][j][k] \times \frac{p[i+1]^x}{x!}

时间复杂度为 O(nmk^2)

C++ 代码示例

# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N = 55;
const int mo = 998244353;
int f[N][N][N], fac[N], p[N];
int Pow(int x, int n) {
	int ans = 1;
	while (n) {
		if (n & 1) ans = ans * x % mo;
		x = x * x % mo;
		n >>= 1;
	}
	return ans % mo;
}
signed main() {
	int n, m, l, sum = 0; cin >> n >> m >> l;
	for (int i = 1; i <= n; i++) {
		cin >> p[i];  sum += p[i];
	}
	int inv = Pow(sum, mo - 2); f[0][0][0] = 1;
	for (int i = 1; i <= n; i++) {
		p[i] = p[i] * inv % mo;
	}
	fac[0] = 1; for (int i = 1; i <= l; i++) fac[i] = fac[i-1] * i % mo;
	for (int i = 0; i <= n; i++) for (int j = 0; j <= i; j++)
	for (int k = 0; k <= l; k++) for (int c = 0; k + c <= l; c++) 
		(f[i + 1][j + (c != 0)][k + c] += f[i][j][k] * Pow(p[i + 1], c) % mo * Pow(fac[c], mo - 2) % mo) %= mo;	
	int ans = f[n][m][l] * fac[l] % mo;
	cout << ans << endl;
	return 0;
}

G - Sqrt

题意

一开始有长度为 1 的序列,这个序列中只有一个数 x,接下来进行 10^{100} 操作。

每次操作让序列新增一个整数元素,并且这个整数元素的值的取值范围是 1 到当前序列末尾元素的平方根。

求出最终会生成几种不同的序列,答案保证在2^{63}以内。

本题有 T 组数据。

数据范围 1 \leq T \leq 20, 1 \leq x \leq 9\times 10^{18}

题解

f[i] 表示末尾为 i 新增不同序列的数量。 f[i] = \sum_{j = 1} ^{\lfloor \sqrt{i}\rfloor} f[j]

s[i] 为数组 f[i] 的前缀和,则上述转移方程可以写成:f[i] = s[\lfloor \sqrt{i}\rfloor]

初始值为 f[1] = s[1] = 1。而s[n] = \sum_{i = 1} ^ {n} f[i] = \sum_{i = 1} ^ {n} s[\lfloor \sqrt{i}\rfloor]

因此f[x] = \sum_{i = 1} ^{\lfloor \sqrt{x}\rfloor} f[i] = s[\lfloor \sqrt{x}\rfloor] = \sum_{i = 1} ^ {\lfloor \sqrt{x}\rfloor} s[\lfloor \sqrt{i}\rfloor]

只需要进行 O(x^{1/4})的预处理即可。

注意大整数开根号取整直接用sqrt(),可能出现精度问题。

一种可行的办法是用二分代替。

时间复杂度为 O(Tx^{1/4})

C++ 代码示例

# include <bits/stdc++.h>
# define int unsigned long long
using namespace std;
const int N = 1e6 + 10;
int f[N], s[N];
int getsqr(int x) {
	int l = 0, r = 3e9 + 1, ans;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (mid * mid <= x) ans = mid, l = mid + 1;
		else r = mid - 1;
	}
	return ans;
}
signed main() {
	int T; cin >> T;
	while (T--) {
		int n; cin >> n;
		int h = getsqr(n), l = getsqr(h), ans = 0;
		f[1] = s[1] = 1;
		for (int i = 2; i <= l; i++) {
			f[i] = s[getsqr(i)];
			s[i] = s[i - 1] + f[i];
		}
		for (int i = 1; i <= l; i ++) {
			int l = i * i, r = min((i + 1) * (i + 1) - 1, h);
			ans += (r - l + 1) * s[i];
		}
		cout << ans <<endl;
	}
	return 0;
}
posted @   Maystern  阅读(113)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 2025年广告第一单,试试这款永久免费的开源BI工具
· 为什么 .NET8线程池 容易引发线程饥饿
· 场景题:假设有40亿QQ号,但只有1G内存,如何实现去重?
· 在 .NET 中使用 Tesseract 识别图片文字
· .NET 响应式编程 System.Reactive 系列文章(一):基础概念
点击右上角即可分享
微信分享提示