ABC238 复盘

ABC238 复盘

upd in 2024/3/1,添加了时间复杂度统计和代码注释。

[ABC238A] Exponential or Quadratic

思路解析

通过“指数爆炸”的特点可以发现当 \(n \ge 5\)\(n = 1\)\(2^n\) 是大于 \(n^2\) 的,所以一个 if 即可。

时间复杂度:只有一个 if,因此为 \(O(1)\)

code

#include<bits/stdc++.h>
using namespace std;
int main() {
	int n;
	cin >> n;
	if(n >= 5 || n == 1) cout << "Yes";	//直接判断
	else cout << "No";
	return 0;
}

[ABC238B] Pizza

思路解析

切披萨,如果披萨的第 \(i^\circ\) 上被切开了,我们就将 \(i\) push_back 进一个 vector 中,最后排序统计相邻两个元素之间的距离就是比萨的角度。

注意 \(0^\circ\) 也有一道切口。

时间复杂度:排序+遍历,为 \(O(n \log n)\)

code

#include<bits/stdc++.h>
using namespace std;
const int N = 400;
int n, a[N];
int main() {
	cin >> n;
	int idx = 0;
	vector<int> v;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
		idx = (idx + a[i]) % 360;
		v.push_back(idx);	//将需要切割的位置加入 vector
	}
	v.push_back(0); v.push_back(360);
	sort(v.begin(), v.end());	//排序
	int ans = -1;
	for(int i = 0; i < v.size() - 1; i++) {
		ans = max(ans, v[i + 1] - v[i]);	//统计最大的角度
	}
	cout << ans;
	return 0;
}

[ABC238C] digitnum

思路解析

找规律发现,\(f(i)=x-10^{\lfloor \log_{10}x \rfloor}+1\),于是就对于每一个 \(\lfloor \log_{10}x \rfloor = i\) 的数,单独统计这些数对答案的贡献即可。

时间复杂度:对于十进制下每一个长度都遍历了一遍,因此为 \(O(\lfloor \log_{10}n \rfloor)\)

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define il __int128
const il mod = 998244353;
ll n;
il sum(il x) {
	return ((x + 1) * x / 2) % mod;
}
int main() {
	cin >> n;
	il s = 1, ans = 0;
	while(s <= n) {	//枚举十进制下每一位
		ans = (ans + sum(min((il)n, s * (il)10 - 1) - s + 1)) % mod;	//统计当前位的贡献
		s = s * 10;
	}
	cout << (ll)ans;
	return 0;
}

[ABC238D] AND and SUM

思路解析

首先可以很简单地发现 \(x,y\) 都大于等于 \(a\),所以如果 \(2 \times s > a\) 答案就绝对是 No。其次,还需要判断除了 \(a\) 以外的其它位上的值能否填补上 \(s-a\),若位数不够则输出No,否则输出 Yes

时间复杂度:需要遍历 \(a\) 的二进制上的每一位,因此为 \(T \log a\)

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll t, a, s;
bitset<80> bs;
int main() {
	cin >> t;
	while(t--) {
		bs ^= bs;
		cin >> a >> s;
		for(int i = 0; i <= 60; i++) {
			bs[i] = ((a >> i) & 1);
		}
		ll b = a + a;
		for(int i = 60; i >= 0; i--) {
			if(bs[i]) continue;
			if(b + (1ll << i) <= s) b += (1ll << i);	//枚举每一位,尝试加上当前维的值
		}
		if(b == s) cout << "Yes\n";	//可以相等,输出 Yes
		else cout << "No\n";
	}
	return 0;
}

[ABC238E] Range Sums

思路解析

首先我们发现如果我们知道了区间 \([1,l-1]\) 和区间 \([l,r]\),我们就能知道 \([1,r]\)。同理如果我们知道 \([1,x]\)\([1,y]\),我们就能知道 \([x,y]\)。这就意味着如果我们知道了区间 \([l,r]\),也就是相当于连通了 \([1,l-1]\)\([1,r]\),这时如果我们也知道 \([1,r-1]\),我们就能知道 \([1,r]\),于是便可以很轻松将这道题转换成一个类似连通块的问题,目标就是求 \([1,0]\)\([1,n]\) 是否连通,于是用并查集维护是否连通即可。

时间复杂度:\(q\) 次并查集合并操作,路径压缩并查集 find 函数复杂度 \(O(\log n)\),因此总体复杂度为 \(O(q \log n)\)

code

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, q, fa[N];
int find(int x) {
	if(fa[x] == x) return x;
	else return (fa[x] = find(fa[x]));
}
int main() {
	cin >> n >> q;
	for(int i = 1; i <= n; i++) fa[i] = i;
	while(q--) {
		int l, r;
		cin >> l >> r;
		fa[find(l - 1)] = find(r);	//代表连通两个区间
	}
	if(find(0) == find(n)) cout << "Yes";	//查询 0 和 n 是否在一个区间
	else cout << "No";
	return 0;
}

[ABC238F] Two Exams

思路解析

这题很麻烦,因为有两个维度。所以可以想到先按照第一维排序,这样就不需要考虑第二维的问题。其次再发现数据范围小,可以想到能用 dp 做,接下来就考虑如何 dp。首先我们要知道我们遍历到了第几个公民,同时还要知道还剩下几个代表名额,同时我们还需要思考第二维对选择代表的影响。于是想到 \(f_{i,j,k}\) 表示考虑完了前 \(i\) 个人,选了 \(j\) 个公民做代表,在没有被选为代表的人中第二维的值最小的值是 \(k\)。原因是我们已经按照第一位排好了序,当前的人在第一维上的排名一定落后于第二维是 \(k\) 的人,如果当前人在第二维上依然落后于 \(k\),就必定不可以选。

然后考虑状态转移,分为选和不选两种情况。

  • 如果不选,\(j\) 不变,但需要更新 \(k\),所以可得 \(f_{i,j,\min(k,q_{i})} \gets f_{i-1,j,k}\)

  • 如果选,则 \(j\) 需要加一,同时比较 \(q_{i}\)\(k\),可得 \(f_{i,j,k} \gets f_{i-1,j-1,k}\)

最后的答案就是 \(\sum_{i=1}^{n+1}f_{n,m,i}\)

细节:初始时没有一个人不选,\(k\)\(n+1\),以及统计答案时也有可能没有一个人不选,记得遍历到 \(n+1\)

时间复杂度:第一维枚举每一个人,第二维枚举选几个人,第三维枚举当前最小值,因此为 \(O(mn^{2})\)

code

#include<bits/stdc++.h>
using namespace std;
#define PII pair<int, int>
#define fir first
#define sec second
const int N = 310, mod = 998244353;
int n, m, f[N][N][N];
PII a[N];
int main() {
	cin >> n >> m;
	for(int i = 1; i <= n; i++) {
		cin >> a[i].fir; 
	}
	for(int i = 1; i <= n; i++) {
		cin >> a[i].sec;
	}
	sort(a + 1, a + n + 1, [](PII x, PII y) { return x.fir < y.fir;});
	f[0][0][n + 1] = 1;
	for(int i = 1; i <= n; i++) {
		for(int j = 0; j <= m; j++) {
			for(int k = 1; k <= n + 1; k++) {
				f[i][j][min(a[i].sec, k)] = (f[i][j][min(a[i].sec, k)] + f[i - 1][j][k]) % mod;	//不选的情况
				if(j > 0 && a[i].sec < k) {	//判断,不能使当前人的排名落后于 k
					f[i][j][k] = (f[i][j][k] + f[i - 1][j - 1][k]) % mod; //选的情况
				}
			}
		}
	}
	int ans = 0;
	for(int i = 1; i <= n + 1; i++) ans = (ans + f[n][m][i]) % mod;	//统计以每一个数结尾的情况
	cout << ans;
	return 0;
}
posted @ 2024-02-29 22:52  2020luke  阅读(69)  评论(0)    收藏  举报