AtCoder Beginner Contest 243

AtCoder Beginner Contest 243 Solution

A - Shampoo

题意

牙膏的体积为 \(V\)F 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_i\)\(U\) ,表示走到当前节点的父亲节点。
  • \(s_i\)\(L\) ,表示走到当前节点的左儿子节点。
  • \(s_i\)\(R\) ,表示走到当前节点的右儿子节点。

请输出最终走到的节点标号,保证答案在\([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_i\)\(b_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 @ 2022-03-24 17:57  Maystern  阅读(107)  评论(0编辑  收藏  举报