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

给定一个 1n 的排列 p,已知 p1n 的所有排列中字典序排名第 k 小的,求字典序第 k1 小的排列。

1n100

看到题面,非常熟悉,直接进行一个康托展开的写(

因为 n100,所以并不用康托展开。

upd:艹,我 sb 了,直接 std::prev_permutation 即可,下面的东西不用看了 qwq。

设我们要求的排列为 p

我们发现,一定存在一个位置 j,使得 pj+1<pj+2<<pnpj+1>pj+2>>pn

1n 枚举 j,若满足条件,将 pj+1pnpj 的前驱与 pj 交换,然后降序排序即可。

时间复杂度 O(n2)

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

给定 n 个数 a1an,一次操作可以选择一个数除以 2 或 3(前提是这个数是 2 或 3 的倍数),问使得所有数都相等的最小操作次数。

显然,所有数相等的最优方案是它们的最大公约数。

把最大公约数求出来,剩下的部分拆分,如果能完全分解为 2,3 则可行,输出次数。

时间复杂度 O(nlogai)

E - Round Trip

给定一张 n×m 的网格图,其中有障碍和可行点,其中一个点是起点。问是否能从起点出发不经过重复的点再回到起点。

1n×m106

直接从四个方向 DFS 即可,用了 std::map,故时间复杂度为 O(nmlognm)

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

给定 n 个正整数 a1an,对于 k=1,2,,n,你需要:

  • 两次从 a1ak 中均等概率地选取一个取出,记录数值并放回。

  • 你的得分为两次取出数值的较大值。

对于每个 k,求出得分的期望值。

1n,ai2×105

之前在 Froggy 的 ARC 题解中看到过一道类似的题目,然后就照着那道题瞎搞,结果崩了 /kk,浪费了好多时间。

先将期望转化为总和除以总次数,总次数显然为 k2,只考虑计算总和。

要注意到 ai 的小范围,正解显然跟这个有关。

ans=j=1iaj,考虑新加入一个 ai 对答案的贡献(两次选的下标相同的情况由 ans 计算,此处仅考虑两次下标取的不同的情况):

x 为除 ai 外另选的一个数。

  • xai 时,ai 对答案产生贡献。

  • x>ai 时,x 对答案产生贡献。

用值域树状数组统计出 ai 的数的数量及 >ai 的数的总和,分别记为 res,val,则当前答案为:

ans+2×(ai×res+val)i2

此处 2× 是因为操作的顺序也有影响。

时间复杂度 O(nlogn)

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

构造一个长度为 n 的序列 a,满足:

  • 0a1a2anm

  • i[1,n),aiai+1(mod3)

求构造方案数。

1n,m107

翻了翻 bot 和其他几位大佬的代码,好像没有我这么写的,讲一讲我的想法。

我们考虑构造 a 的差分数组 b,这样只需要满足 i=1nbim

只是这样的话也没什么意义,还有合法性的问题,考虑只用 1,2 填充数组,这样构造的序列一定是合法的,之后我们再把 3 用插板法加到数组里的某些位置即可。

然后我考场上跟个 zz 一样,一直以为不用插板法,快速幂也是可以的 qwq。

具体而言,枚举 2 的数量,记为 i,且满足 i×2+(ni)=i+nm

此时的方案数为 (ni)×k=0mni3(n+k1n1)

后面那串式子和 i 没关系,直接预处理出来即可。

此时还要注意到,可能会有 b10(mod3) 的情况,但我们强行用 1,2 填充了序列,这样会漏算。

这个问题也很简单,强令 b1 初始为 0,然后解决 n1 的子问题即可。

时间复杂度 O(n+m)

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。

posted @   ImALAS  阅读(486)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示