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

给定一个 \(1\sim n\) 的排列 \(p\),已知 \(p\)\(1\sim n\) 的所有排列中字典序排名第 \(k\) 小的,求字典序第 \(k-1\) 小的排列。

\(1\le n\le 100\)

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

因为 \(n\le 100\),所以并不用康托展开。

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

设我们要求的排列为 \(p'\)

我们发现,一定存在一个位置 \(j\),使得 \(p_{j+1}\lt p_{j+2}\lt\dots\lt p_n\)\(p'_{j+1}\gt p'_{j+2}\gt\dots\gt p'_n\)

\(1\)\(n\) 枚举 \(j\),若满足条件,将 \(p_{j+1}\sim p_{n}\)\(p_j\) 的前驱与 \(p_j\) 交换,然后降序排序即可。

时间复杂度 \(\mathcal O(n^2)\)

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\) 个数 \(a_1\sim a_n\),一次操作可以选择一个数除以 2 或 3(前提是这个数是 2 或 3 的倍数),问使得所有数都相等的最小操作次数。

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

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

时间复杂度 \(\mathcal O(n\log a_i)\)

E - Round Trip

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

\(1\le n\times m\le 10^6\)

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

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\) 个正整数 \(a_1\sim a_n\),对于 \(k=1,2,\dots,n\),你需要:

  • 两次从 \(a_1\sim a_k\) 中均等概率地选取一个取出,记录数值并放回。

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

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

\(1\le n,a_i\le 2\times 10^5\)

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

先将期望转化为总和除以总次数,总次数显然为 \(k^2\),只考虑计算总和。

要注意到 \(a_i\) 的小范围,正解显然跟这个有关。

\(ans=\sum\limits_{j=1}^i a_j\),考虑新加入一个 \(a_i\) 对答案的贡献(两次选的下标相同的情况由 \(ans\) 计算,此处仅考虑两次下标取的不同的情况):

\(x\) 为除 \(a_i\) 外另选的一个数。

  • \(x\le a_i\) 时,\(a_i\) 对答案产生贡献。

  • \(x\gt a_i\) 时,\(x\) 对答案产生贡献。

用值域树状数组统计出 \(\le a_i\) 的数的数量及 \(\gt a_i\) 的数的总和,分别记为 \(res,val\),则当前答案为:

\[\frac{ans+2\times(a_i\times res+val)}{i^2} \]

此处 \(2\times\) 是因为操作的顺序也有影响。

时间复杂度 \(\mathcal O(n\log n)\)

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\),满足:

  • \(0\le a_1\le a_2\le \dots \le a_n\le m\)

  • \(\forall i\in [1,n),a_i\not\equiv a_{i+1}\pmod{3}\)

求构造方案数。

\(1\le n,m\le 10^7\)

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

我们考虑构造 \(a\) 的差分数组 \(b\),这样只需要满足 \(\sum\limits_{i=1}^n b_i\le m\)

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

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

具体而言,枚举 2 的数量,记为 \(i\),且满足 \(i\times 2+(n-i)=i+n\le m\)

此时的方案数为 \(\binom{n}{i}\times \sum\limits_{k=0}^{\lfloor\frac{m-n-i}{3} \rfloor} \binom{n+k-1}{n-1}\)

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

此时还要注意到,可能会有 \(b_1\equiv 0\pmod{3}\) 的情况,但我们强行用 1,2 填充了序列,这样会漏算。

这个问题也很简单,强令 \(b_1\) 初始为 0,然后解决 \(n-1\) 的子问题即可。

时间复杂度 \(\mathcal 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 @ 2022-11-06 00:31  ImALAS  阅读(434)  评论(3编辑  收藏  举报