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;
}

浙公网安备 33010602011771号