AtCoder Beginner Contest 276 A~G 题解
今天凌晨 CF D 题差一句判断 AC,晚上 ABC G 题把插板法和快速幂搞混差点 AC。
事不过三,再这样一次我直接紫砂。
太简单的题就不放翻译和代码了。(事实上这场 A-E 都是大水题。。)
Ex 太难了先润了,以后再说吧。
A - Rightmost
模拟即可。
B - Adjacency List
std::set
模拟即可。
C - Previous Permutation
给定一个 的排列 ,已知 是 的所有排列中字典序排名第 小的,求字典序第 小的排列。
看到题面,非常熟悉,直接进行一个康托展开的写(
因为 ,所以并不用康托展开。
upd:艹,我 sb 了,直接 std::prev_permutation
即可,下面的东西不用看了 qwq。
设我们要求的排列为 。
我们发现,一定存在一个位置 ,使得 且
从 到 枚举 ,若满足条件,将 中 的前驱与 交换,然后降序排序即可。
时间复杂度
Code
// Problem: C - Previous Permutation // Contest: AtCoder - AtCoder Beginner Contest 276 // URL: https://atcoder.jp/contests/abc276/tasks/abc276_c // Memory Limit: 1024 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) #include <bits/stdc++.h> using i64 = long long; using pii = std::pair<int,int>; #define fir first #define sec second #define Chtholly std::set<node>::iterator const i64 mod = 1e9 + 7; const int maxn = 5e5 + 5; const int maxm = 3e6 + 5; const int maxk = 30; int n,m,k,a[maxn]; int main() { scanf("%d",&n); for(int i = 1;i <= n;++ i) scanf("%d",&a[i]); for(int i = 1;i <= n;++ i) { bool flag = true; for(int j = i + 2;j <= n;++ j) { if(a[j] < a[j - 1]) { flag = false; break ; } } if(!flag)continue ; int pos = 0; for(int j = i + 1;j <= n;++ j) { if(a[j] < a[i]) { if(a[j] > a[pos])pos = j; } } std::swap(a[i] , a[pos]); std::sort(a + i + 1 , a + n + 1 , std::greater<int>()); for(int j = 1;j <= n;++ j)printf("%d ",a[j]); return 0; } return 0; }
D - Divide by 2 or 3
给定 个数 ,一次操作可以选择一个数除以 2 或 3(前提是这个数是 2 或 3 的倍数),问使得所有数都相等的最小操作次数。
显然,所有数相等的最优方案是它们的最大公约数。
把最大公约数求出来,剩下的部分拆分,如果能完全分解为 2,3 则可行,输出次数。
时间复杂度
E - Round Trip
给定一张 的网格图,其中有障碍和可行点,其中一个点是起点。问是否能从起点出发不经过重复的点再回到起点。
直接从四个方向 DFS 即可,用了 std::map
,故时间复杂度为 。
Code
// Problem: E - Round Trip // Contest: AtCoder - AtCoder Beginner Contest 276 // URL: https://atcoder.jp/contests/abc276/tasks/abc276_e // Memory Limit: 1024 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) #include <bits/stdc++.h> using i64 = long long; using pii = std::pair<int,int>; #define fir first #define sec second #define Chtholly std::set<node>::iterator const i64 mod = 1e9 + 7; const int maxn = 5e5 + 5; const int maxm = 3e6 + 5; const int maxk = 30; int n,m,s,t,fx[] = {0 , -1 , 0 , 1},fy[] = {1 , 0 , -1 , 0}; std::map<int,bool> vis[maxn],inq[maxn]; bool dfs(int x,int y,int dep) { if(inq[x][y]||vis[x][y])return false; if(x == s&&y == t&&dep > 1)return true; vis[x][y] = true; for(int k = 0;k < 4;++ k) { int nx = x + fx[k],ny = y + fy[k]; if(nx >= 1&&nx <= n&&ny >= 1&&ny <= m) { if(nx == s&&ny == t&&!dep)continue ; if(inq[nx][ny]||inq[x][y])continue ; if(dfs(nx , ny , dep + 1))return true; } } return false; } int main() { scanf("%d %d",&n,&m); for(int i = 1;i <= n;++ i) { char c; for(int j = 1;j <= m;++ j) { scanf(" %c",&c); if(c == 'S') { s = i; t = j; } else if(c == '#')inq[i][j] = true; } } for(int k = 0;k < 4;++ k) { for(int i = 1;i <= n;++ i) vis[i].clear(); int x = s + fx[k],y = t + fy[k]; if(x >= 1&&x <= n&&y >= 1&&y <= m) { if(dfs(x , y , 0)) { puts("Yes"); return 0; } } } puts("No"); return 0; }
F - Double Chance
给定 个正整数 ,对于 ,你需要:
两次从 中均等概率地选取一个取出,记录数值并放回。
你的得分为两次取出数值的较大值。
对于每个 ,求出得分的期望值。
之前在 Froggy 的 ARC 题解中看到过一道类似的题目,然后就照着那道题瞎搞,结果崩了 /kk,浪费了好多时间。
先将期望转化为总和除以总次数,总次数显然为 ,只考虑计算总和。
要注意到 的小范围,正解显然跟这个有关。
令 ,考虑新加入一个 对答案的贡献(两次选的下标相同的情况由 计算,此处仅考虑两次下标取的不同的情况):
记 为除 外另选的一个数。
-
当 时, 对答案产生贡献。
-
当 时, 对答案产生贡献。
用值域树状数组统计出 的数的数量及 的数的总和,分别记为 ,则当前答案为:
此处 是因为操作的顺序也有影响。
时间复杂度
Code
// Problem: F - Double Chance // Contest: AtCoder - AtCoder Beginner Contest 276 // URL: https://atcoder.jp/contests/abc276/tasks/abc276_f // Memory Limit: 1024 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) #include <bits/stdc++.h> using i64 = long long; using pii = std::pair<long long,int>; #define fir first #define sec second #define Chtholly std::set<node>::iterator const i64 mod = 998244353; const int maxn = 5e5 + 5; const int maxm = 3e6 + 5; const int maxk = 30; int n,m,k,a[maxn]; i64 power(i64 x,i64 y) { x %= mod; i64 ans = 1; for(;y;y >>= 1) { if(y & 1)(ans *= x) %= mod; (x *= x) %= mod; } return ans; } int cnt,c[maxn]; i64 sum[maxn]; int lowbit(int x) { return x & -x; } void add(int x,int y) { for(;x <= cnt;x += lowbit(x)) ++ c[x],sum[x] += y; return ; } int querynum(int x) { int res = 0; for(;x;x -= lowbit(x)) res += c[x]; return res; } i64 querysum(int x) { i64 ans = 0; for(;x;x -= lowbit(x)) ans += sum[x]; return ans; } int main() { scanf("%d",&n); cnt = 2e5; i64 ans = 0,val = 0; for(int i = 1;i <= n;++ i) { scanf("%d",&a[i]); (ans += a[i]) %= mod; (val += 2ll * (1ll * a[i] * querynum(a[i]) % mod + 1ll * (querysum(cnt) - querysum(a[i]))) % mod) %= mod; printf("%lld\n",(ans + val) % mod * power(1ll * i * i % mod , mod - 2) % mod); add(a[i] , a[i]); } return 0; }
G - Count Sequences
构造一个长度为 的序列 ,满足:
求构造方案数。
翻了翻 bot 和其他几位大佬的代码,好像没有我这么写的,讲一讲我的想法。
我们考虑构造 的差分数组 ,这样只需要满足 。
只是这样的话也没什么意义,还有合法性的问题,考虑只用 1,2 填充数组,这样构造的序列一定是合法的,之后我们再把 3 用插板法加到数组里的某些位置即可。
然后我考场上跟个 zz 一样,一直以为不用插板法,快速幂也是可以的 qwq。
具体而言,枚举 2 的数量,记为 ,且满足 。
此时的方案数为 。
后面那串式子和 没关系,直接预处理出来即可。
此时还要注意到,可能会有 的情况,但我们强行用 1,2 填充了序列,这样会漏算。
这个问题也很简单,强令 初始为 0,然后解决 的子问题即可。
时间复杂度 。
Code
// Problem: G - Count Sequences // Contest: AtCoder - AtCoder Beginner Contest 276 // URL: https://atcoder.jp/contests/abc276/tasks/abc276_g // Memory Limit: 1024 MB // Time Limit: 4000 ms // // Powered by CP Editor (https://cpeditor.org) #include <bits/stdc++.h> using i64 = long long; const i64 mod = 998244353; const int maxn = 2e7 + 5; i64 power(i64 x,i64 y) { if(y < 0)return 0; i64 ans = 1; for(;y;y >>= 1) { if(y & 1)(ans *= x) %= mod; (x *= x) %= mod; } return ans; } i64 frac[maxn],inv[maxn],pw[4000000]; i64 C(int n,int m) { if(n < 0||m < 0||n < m)return 0; return frac[n] * inv[m] % mod * inv[n - m] % mod; } int m; i64 solve(int n,int QAQ) { i64 ans = 0; pw[0] = 1; for(int i = 1;i <= m / 3;++ i) { pw[i] = (pw[i - 1] + C(QAQ + i - 1 , QAQ - 1)) % mod; } for(int i = 0;i <= n;++ i) { if(i + n > m)break ; (ans += C(n , i) * pw[(m - n - i) / 3] % mod) %= mod; } return ans; } int main() { int n; scanf("%d %d",&n,&m); frac[0] = 1; for(int i = 1;i <= n + m;++ i) frac[i] = 1ll * (i64)i * frac[i - 1] % mod; inv[n + m] = power(frac[n + m] , mod - 2); for(int i = n + m - 1;~ i;-- i) inv[i] = 1ll * (i64)(i + 1) * inv[i + 1] % mod; printf("%lld\n",(solve(n , n) + solve(n - 1 , n)) % mod); return 0; }
题外话,还有两周左右 NOIp2022 就要开考了,但是 HA 这疫情形势真的还能考吗 QAQ。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)