Atcoder Beginner Contest 赛后总结
ABC 407
赛时表现
00:02 速切 A 题
00:04 速切 B 题
00:08 速切 C 题
00:19 D 题写出正解,但看错范围(\(HW \le 20\) 看成了 \(H,W \le 20\)),手搓 \(H = W = 20\) 的样例后发现超时
00:41 尝试优化,写了一版非递归式的,仍然超时
00:46 E 题题目没读懂
01:11 F 题读完题,先写了一份单调队列的暴力代码,调试了 5 分钟
01:17 继续优化 D 题,没有思路,抱着试一试的态度交了一发,过了(写总结才发现题目读错了)
01:40 E 题没有思路,尝试优化 F 题,感觉可以单调栈加上一些计算来搞,但没有具体的思路
赛后反思
-
D 题读题有误,把数据范围看错了,从而写出正解但没交
-
E 题没有读懂题目,也没有思路
ABC 406
赛时表现
00:03 速切 A 题
00:06 速切 B 题
00:16 写出 C 题第一版做法,对凹凸都做一遍前后缀和,对于每个点通过前后面凹凸的位置计算答案,发现第三个样例算多了
00:20 debug 失败,选择先开 D 题
00:23 想到可以暴力维护,直接开写
00:37 写完,一发通过
00:46 看完 E 题,尝试打表找规律,失败
00:59 重构 C 题代码,仍然算重
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
int n,a[N];
long long ans;
int pre[N],suf[N];
void solve() {
cin >> n;
for(int i = 1;i <= n;i ++) {
cin >> a[i];
}
pre[0] = 0,suf[n] = n;
for(int i = 2;i < n;i ++) {
pre[i] = pre[i - 1];
if(a[i] > a[i - 1] && a[i] > a[i + 1]) {
pre[i] = i;
}
}
for(int i = n - 1;i > 1;i --) {
suf[i] = suf[i + 1];
if(a[i] < a[i - 1] && a[i] < a[i + 1]) {
suf[i] = i;
}
}
for(int i = 1;i <= n;i ++) {
if(pre[i] == i) {
int l = i,r = suf[i];
int L = pre[i - 1],R = suf[suf[i] + 1];
cout << l << " " << r << " " << L << " " << R << "\n";
ans += 1LL * (l - L) * (R - r);
}
}
cout << ans << "\n";
}
int main() {
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cout << fixed << setprecision(12);
int t = 1;
// cin >> t;
while(t --) {
solve();
}
return 0;
}
01:11 E 题想到动态规划,开始写,最初状态 dp[第几位][几位是1][是否受限] = 数字和,开始写,写到转移方程时发现不好转移
01:23 C 题想到枚举位置,二分下一个位置,实现后发现二分出的位置不对,答案有问题
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
int n,a[N];
long long ans;
int pre[2][N];
int find1(int l,int r) {
while(l <= r) {
int mid = l + r >> 1;
if(pre[0][mid] - pre[1][mid] >= 1 && pre[1][mid] - pre[1][l] >= 1) {
l = mid + 1;
}
else {
r = mid - 1;
}
}
return r;
}
int find2(int l,int r) {
while(l <= r) {
int mid = l + r >> 1;
if(pre[0][mid] - pre[1][mid] <= 1 && pre[1][mid] - pre[1][l] <= 1) {
l = mid + 1;
}
else {
r = mid - 1;
}
}
return r;
}
void solve() {
cin >> n;
for(int i = 1;i <= n;i ++) {
cin >> a[i];
}
for(int i = 2;i < n;i ++) {
pre[0][i] = pre[0][i - 1],pre[1][i] = pre[1][i - 1];
pre[0][i] += a[i] > a[i - 1] && a[i] > a[i + 1];
pre[1][i] += a[i] < a[i - 1] && a[i] < a[i + 1];
}
for(int i = 1;i <= n;i ++) {
int j = find1(i,n),k = find2(i,n);
cout << i << " " << j << " " << k << "\n";
ans += max(0,k - j);
}
cout << ans << "\n";
}
int main() {
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cout << fixed << setprecision(12);
int t = 1;
// cin >> t;
while(t --) {
solve();
}
return 0;
}
01:31 E 题想到使用两个 DP 数组,dp[第几位][几位是1][是否受限] = 数字个数,dp2[第几位][几位是1][是否受限] = 数字和,开始写
01:39 没设初始状态,答案算少了,思考后得出要倒着推,极限写完(但是写的时候并没有意识到是数位DP)
// Code by iamsh
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
long long n,k,l;
vector<int> bit;
long long dp1[61][61][2];
long long dp2[61][61][2];
void solve() {
cin >> n >> k;
bit.clear();
while(n) {
bit.push_back(n & 1);
n >>= 1;
}
reverse(bit.begin(),bit.end());
int l = bit.size();
vector<long long> w(l);
for(int i = 0;i < l;i ++) {
w[i] = 1LL << l - i - 1;
}
memset(dp1,0,sizeof dp1);
memset(dp2,0,sizeof dp2);
dp1[l][k][0] = dp1[l][k][1] = 1;
for(int p = l - 1;~p;p --) {
for(int c = 0;c <= k;c ++) {
for(int t = 0;t < 2;t ++) {
long long nc = 0,ns = 0;
for(int b = 0;b < 2;b ++) {
if((t && b > bit[p]) || b + c > k) {
continue;
}
int nt = t && b == bit[p];
nc = (nc + dp1[p + 1][b + c][nt]) % mod;
ns = (ns + 1LL * b * w[p] % mod * dp1[p + 1][b + c][nt]) % mod;
ns = (ns + dp2[p + 1][b + c][nt]) % mod;
}
dp1[p][c][t] = nc,dp2[p][c][t] = ns;
}
}
}
cout << dp2[0][0][1] % mod << "\n";
}
int main() {
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cout << fixed << setprecision(12);
int t = 1;
cin >> t;
while(t --) {
solve();
}
return 0;
}
赛后总结
-
C 题没有想到正解思路(只想到了计算点是凹还是凸):处理一个点和前面点的高低关系,发现一定是高低高的顺序。
-
没有及时看 F 题,F 题直接树剖线段树树状数组就可以解决

浙公网安备 33010602011771号