【比赛】2021牛客寒假算法基础集训营1 题解

前言

太惨了太惨了。

比赛入口

点此进入查看比赛题目


A 串

这题我看到题目以为要用容斥原理做,想了好久都没想出来,想到底该如何去减少重复的次数,然后就没了😟
对一个序列进行处理,并且有几种状态,目标求数量那么要尝试去使用DP去做。
DP表示为二维数组, f [ i ] [ j ] f[i][j] f[i][j]代表前i个字符串中,状态为j,属性为数量的集合。

  • j = 0 j = 0 j=0 代表无u,表示前i - 1个字符串中没有包含u且第i为不取u的状态。
  • j = 1 j = 1 j=1 代表有u无s,表示前i - 1个字符串中包含u第i位取除s以外字母的状态 + 前i - 1个字符串中不包含u第i位取u的状态。
  • j = 2 j = 2 j=2 代表有us,表示前i - 1个字符串中包含u第i位取s的状态 + 前i - 1个字符串中包含us第i位任意取字母的状态。

虽然不是自己想出来的,但是写出来好有成就感(hhh~),感觉DP还是蛮有意思的。

AC代码

#include<bits/stdc++.h>
using namespace std;

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 1000000 + 10, mod = 1e9 + 7;
ll f[N][3];

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);
	int n;
	cin >> n;

	f[1][0] = 25;
	f[1][1] = 1;
	f[1][2] = 0;
	ll ans = 0;
	
	_rep(i, 2, n)
	{
		f[i][0] = f[i - 1][0] * 25 % mod;
		f[i][1] = (f[i - 1][1] * 25 + f[i - 1][0])% mod;
		f[i][2] = (f[i - 1][1] + f[i - 1][2] * 26) % mod;
		ans = (ans + f[i][2]) % mod;
	}
	cout << ans << endl;
	return 0;
}


B 括号

这个题目我是用模拟做出来的。
可以发现 ( ( ( ) ) ) ((())) ((())) 获得的括号数是3 + 3 + 3, 把第三个左括号往右移动 ( ( ) ( ) ) (()()) (()())就会减少一个得到3 + 3 + 2。
根据这个规律,我们首先先找到他需要几个括号把k取根号向上取整即可得到有几对括号,然后让中间的括号一直往右移动,直到端点,然后换下一个移动,移动的同时将括号数减少,直到减少到k值,那么就停止移动。

AC代码

#include<bits/stdc++.h>
using namespace std;

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 100000 + 10, INF = 0x3f3f3f3f;
int k, g[N], n;
ll x;

void solve()
{
	For(i, n - 1, 0) _for(j, i + 1, 2 * n)
	{
		x--;
		swap(g[j], g[j - 1]);
		if (x == k) return;
	}
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);

	cin >> k;
	if (k == 0) {
		cout << ")(" << endl;
		return 0;
	}
	n = ceil(sqrt(k));
	_for(i, 0, n) g[i] = 1; // 1 代表 左括号, 0代表右括号
	x = n * 1LL * n;
	
	if (x != k) solve();
	_for(i, 0, 2 * n) {
		if (g[i]) cout << '(';
		else cout << ')';
	}
	cout << endl;
	return 0;
}

C 红和蓝

我们要抓住一个关键点,就是每个颜色周围只能有一种颜色是相同颜色, 所以说,每对颜色是相间排列的。
设u为树的一个节点,如果它的下面有偶数个节点,那么他下面的节点就可以让每对颜色相间排列。如果后面有奇数个节点,那么让他下面的第一个节点与他颜色相同,其他节点每对颜色成对排列。
这个可能还是有点没说明白,下面将加上详细注释的代码再讲述一遍。

AC代码

#include<bits/stdc++.h>
using namespace std;

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 100000 + 10, mod = 1e9 + 7;
int h[N], e[N], ne[N], idx, n;
int f[N]; //代表以i为根的子树下面有多少个结点。
int color[N]; // 1 代表红色, 0 代表蓝色
bool fail; // 匹配结果 

void add(int a, int b) // 拉链法 数组模拟链表
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int cal(int u, int fa) // 计算每个结点后面有多少个结点。
{
	int sum = 1;
	for (int i = h[u]; ~i; i = ne[i])
	{
		int j = e[i];
		if (j != fa) sum += cal(j, u);
	}
	return f[u] = sum;	
}

void dfs(int u, int fa, int c)
{
	if (fail) return;
	color[u] = c;
 	int cnt = 0; // u 有几个分支中的节点数是奇数。
	for (int i = h[u]; ~i; i = ne[i])
	{
		int j = e[i];
		if (j != fa) cnt += f[j] & 1;
	}

	if (color[u] == color[fa])  // 它与它的父节点是相同的颜色, 他的节点只能是偶数个,否则无解
	{
		if (cnt)
		{
			fail = true;
			return;
		}
		// 数量是偶数个,递归下一层,颜色取和他不一样的。
		for (int i = h[u]; ~i; i = ne[i])
		{
			int j = e[i];
			if (j != fa) dfs(j, u, !c);
		}
		return;
	}

	// 它与它的父结点颜色不一样,说明他的具有奇数个数的儿子节点和他取一样的颜色。但是只能是一个儿子结点的结点个数为奇数个。
	if (cnt != 1) fail = true;
	else for (int i = h[u]; ~i; i = ne[i])
	{
		int j = e[i];
		if (j != fa) dfs(j, u, f[j] & 1 ? c : !c);
	}
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);

	memset(h, -1, sizeof h); // 初始化链表
	cin >> n;
	_for(i, 0, n - 1)
	{ // 建图
		int u, v;
		cin >> u >> v;
		add(u, v);
		add(v, u);
	}
	cal(1, -1);
	dfs(1, 0, 1);

	if (fail) cout << -1;
	else _rep(i, 1, n) if (color[i]) cout << 'R'; else cout << 'B';
	cout << endl;
	return 0;
}


D 点一成零

题目中的操作是对连通块进行操作,在脑中匹配算法,尝试去往并查集、bfs、dfs方向想。
通过样例我们发现:

  • 每一种先将哪一个连通块变化是有序的。
  • 操作完之后连通块会保存,延续至下一个操作。

通过第一点我们可以推出式子: n ! ∗ ∏ i = 0 n a i n! * \prod_{i = 0}^{n}a_i n!i=0nai a i a_i ai为每一个联通块中的数量,n为连通块个数因为是有序的,所以要乘以阶乘)。
那么就很明显了,每次操作之后使用并查集去维护连通块中的数量。

AC代码

#include<bits/stdc++.h>
using namespace std;

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 500 + 10, mod = 1e9 + 7;
char g[N][N];
int p[N * N], sz[N * N], cnt, n;
bool vis[N][N];

int find(int x)
{
	return p[x] == x ? x : p[x] = find(p[x]);
}

void merge(int a, int b)
{
	int pa = find(a), pb = find(b);
	if (pa != pb)
	{
		p[pb] = pa;
		sz[pa] += sz[pb];
	}
}

bool inside(int a, int b)
{
	return 0 < a && a <= n && 0 < b && b <= n;
}

int dx[] = { 0, 0, 1, -1 }, dy[] = { 1, -1, 0, 0 };
void bfs(int u, int v)
{
	queue<pii> q;
	q.push({u, v});
	vis[u][v] = true;

	while (!q.empty())
	{
		pii t = q.front();
		q.pop();
		
		int x = t.first, y = t.second;
		_for (i, 0, 4) _for(j, 0, 4)
		{
			int a = x + dx[i], b = y + dy[i];
			if (inside(a, b) && g[a][b] == '1' && !vis[a][b])
			{
				merge(u * n + v, a * n + b);
				vis[a][b] = true;
				q.push({ a, b });
			}
		}
	}
}

int qmi(int a, int b)
{
	ll res = 1, t = a;
	while (b)
	{
		if (b & 1) res = res * t % mod;
		t = t * t % mod;
		b >>= 1;
	}
	return res;
}

int inverse(int a)
{
	return qmi(a, mod - 2);
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);
	
	cin >> n;
	_rep(i, 1, n) cin >> g[i] + 1;
	_rep(i, 1, (n + 1) * (n + 1)) p[i] = i, sz[i] = 1;

	ll ans = 1;
	_rep(i, 1, n) _rep(j, 1, n) if (g[i][j] == '1' && !vis[i][j])
		bfs(i, j), cnt++, ans = ans * cnt % mod * sz[i * n + j] % mod;
	
	int T;
	cin >> T;
	while(T--)
	{
		int x, y;
		cin >> x >> y;
		++x, ++y;
		
		if (g[x][y] == '1')	cout << ans << endl;
		else
		{
			g[x][y] = '1';
			ans = ans * (++cnt) % mod;
			int pa = find(x * n + y);
			_for (i, 0, 4)
			{
				int a = x + dx[i], b = y + dy[i];
				int pb = find(a * n + b);
				if (inside(a, b) && g[a][b] == '1' && pa != pb)
				{
					ans = ans * inverse(cnt--) % mod;
					ans = ans * inverse(sz[pb]) % mod;
					ans = ans * inverse(sz[pa]) % mod;
					merge(pa, pb);
					ans = ans * sz[pa] % mod;
				}
			}
			cout << ans << endl;
		}
	}
	return 0;
}


F 对答案一时爽

分母题。
最少肯定是0,因为就两个人,两个人总有没选到的选项。最多的就是一方全对,然后另一方有答案和他相同的数量就是他的分数。

AC代码

#include<bits/stdc++.h>
using namespace std;

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 100 + 10, INF = 0x3f3f3f3f;
char a[N], b[N];

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);

	int n;
	cin >> n;
	_for(i, 0, n) cin >> a[i];
	_for(i, 0, n) cin >> b[i];
	int cnt = 0;
	_for(i, 0, n) if (a[i] == b[i]) cnt++;
	cout << n  + cnt << " " << 0 << endl;
	
	return 0;
}


H 幂塔个位数的计算

这个题目是欧拉降幂的模板题,如果知道欧拉降幂这个题目很容易做。
a b ≡ { a b % ϕ ( p ) g c d ( a , b ) = 1 a b g c d ( a , b ) ≠ 1 , b < ϕ ( p ) a b % ϕ ( p ) + ϕ ( p ) g c d ( a , b ) ≠ 1 , b ≥ ϕ ( p ) a^b \equiv \begin{cases} a^{b \% \phi(p)} & gcd(a, b) = 1\\ a ^ b & gcd(a, b) \neq 1, b < \phi(p) \\ a^{b \% \phi(p) + \phi(p)} & gcd(a, b) \neq 1, b \geq \phi(p) \end{cases} abab%ϕ(p)abab%ϕ(p)+ϕ(p)gcd(a,b)=1gcd(a,b)=1,b<ϕ(p)gcd(a,b)=1,bϕ(p)
第一个要求a和b互质, 第二三个是广义欧拉降幂,不需要a,b互质,但要根据b的值选择取模。

AC代码

#include<bits/stdc++.h>
using namespace std;

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
#define debug(a) cout << #a << " = " << a << endl;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 100000 + 10;
int primes[N], euler[N], cnt;
bool st[N];

void get_euler()
{
	euler[1] = 1;
	int t;
	for (int i = 2; i < N; ++i)
	{
		if (!st[i])
		{
			euler[i] = i - 1;
			primes[cnt++] = i;
		}
		for (int j = 0; (t = i * primes[j]) < N; ++j)
		{
			st[t] = true;
			if (i % primes[j] == 0)
			{
				euler[t] = euler[i] * primes[j];
				break;
			}
			euler[t] = euler[i] * (primes[j] - 1);
		}
	}
}

ll qmi(ll a, ll b, ll p)
{
	ll res = 1;
	while (b)
	{
		if (b & 1)
		{
			res = res * a;
			if (res > p) res = res % p + p;
		}
		a = a * a;
		if (a > p) a = a % p + p;
		b >>= 1;
	}
	return res;
}

ll solve(ll a, ll b, ll p)
{
	if (p == 1 || !b) return 1;
	return qmi(a, solve(a, b - 1, euler[p]), p);
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);

	get_euler();
	string x, y;
	cin >> x >> y;
	ll sz_x = x.size(), sz_y = y.size(), a = 0, b = 0;
	for (ll i = max(1ll * 0, sz_x - 2); i < sz_x; ++i) a = a * 10 + (x[i] - '0');
	if (sz_y >= 3) b = 100;
	else _for(i, 0, sz_y) b = b * 10 + (y[i] - '0');

	cout << solve(a, b, 10) % 10 << endl;
	return 0;
}


I 限制不互素对的排列

需要注意的是:

  • 0 ≤ k ≤ n / 2 0 \leq k \leq n / 2 0kn/2, 而范围中恰好有 n / 2 个偶数,暗示对偶数位置进行处理。
  • 有序的列相邻两个元素互素。

分类讨论:

  1. 0 ≤ k < n / 2 0 \leq k < n / 2 0k<n/2 时,直接构造,先将偶数输出,后面再按顺序输出。
  2. k = n / 2 k = n / 2 k=n/2时,我们我们发现当存在6和3的时候这个一定有解,不存在即无解。 有解的情况,先把除了6的所有偶数输出来,然后再将6 和 3 输出,剩下的按顺序输出即可。

当范围越来越大的时候,我们可以看出 类似 10 和 5 这样的数也可以做偶数后面的数字输出,但是情况和 6 、3是一样的,所以就统一用 6 、3输出。

AC代码

#include<bits/stdc++.h>
using namespace std;

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 100000 + 10;
bool st[N];

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);

	int n, k;
	cin >> n >> k;

	if (k == n / 2)
	{
		if (n < 6) cout << -1 << endl;
		else {
			st[6] = st[3] = true;
			int x = 2;
			while (x <= n) {
				if (!st[x]) cout << x << " ", st[x] = true;
				x += 2;
			}
			cout << 6 << " " << 3 << " ";
			_rep(i, 1, n) if (!st[i]) cout << i << " ";
			cout << endl;
		}
	}
	else
	{
		cout << 2 << " ";
		st[2] = true;
		int x = 4;
		while (k--) cout << x << " ", st[x] = true, x += 2;
		_rep(i, 1, n) if (!st[i]) cout << i << " ";
		cout << endl;
	}
	return 0;
}


J 一群小青蛙呱蹦呱蹦呱

题目意思就是将 1 − n 1-n 1n中的质数幂排除掉之后的所有数(设为数组a)的最大公倍数。我是用将lcm转化为gcd去做,但是超时了,看完大佬的题解之后发现自己多此一举了,直接从定义入手,不要转化什么什么的。

先介绍一个数学知识:
算术基本定理:任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积 N = P 1 a 1 ∗ P 2 a 2 ∗ P 3 a 3 … … P n a n N=P_1^{a_1}*P_2^{a_2}*P_3^{a_3}……P_n^{a_n} N=P1a1P2a2P3a3Pnan,这里 P 1 P_1 P1小于 P 2 P_2 P2小于 P 3 P_3 P3……小于 P n P_n Pn均为质数,其中指数 a i a_i ai是正整数。

根据基本算术定理,可以推出以下式子:
数组a中的每个值会等于: a i = p 1 k 1 ∗ p 2 k 2 ∗ p 3 k 3 ∗ … … ∗ p n k n a_i = p_1^{k_1} * p_2^{k_2} * p_3^{k_3} * ……* p_n^{k_n} ai=p1k1p2k2p3k3pnkn n > 1 n > 1 n>1
数组a的最大公约数会等于: a n s = p 1 m 1 ∗ p 2 m 2 ∗ p 3 m 3 ∗ … … ∗ p i m i ans = p_1^{m_1} * p_2^{m_2} * p_3^{m_3} * ……* p_i^{m_i} ans=p1m1p2m2p3m3pimi m i m_i mi是为该质数的最大幂, 最大公约数定义。)

那么问题就转化为求每个质数的最大次幂的问题,当 p = 2 p = 2 p=2时,他的最大幂情况为: a i = 2 k ∗ 3 ≤ n a_i = 2^k *3 \leq n ai=2k3n (因为至少要有两个数,只要把另一个质数控制在最小,另一个质数的幂就会最大。), 所以当 p > 2 p > 2 p>2时,同理可得: a i = p k ∗ 2 ≤ n a_i = p^k *2 \leq n ai=pk2n
将k求出来也就是 p i m i p_i^{m_i} pimi 求出来了,代入式子结果就出来了。

n最大为 1.6 e 8 1.6e8 1.6e8 所以质数最大不会超过的值是 8 e 7 8e7 8e7,只需求出这个范围内的质数即可。
求质数用的是线性筛法求质数,这个就自己去百度吧,基础模板就不多讲了。

AC代码

#include<bits/stdc++.h>
using namespace std;

#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define For(i, a, b) for (int i = (a); i >= (b); --i)
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 80000000 + 10, mod = 1e9 + 7;
int primes[N], cnt;
bool st[N];

void get_primes()
{
	for (int i = 2; i < N; ++i)
	{
		if (!st[i]) primes[cnt++] = i;
		for (int j = 0; i * primes[j] < N; ++j)
		{
			st[i * primes[j]] = true;
			if (i % primes[j] == 0) break;
		}
	}
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif
	ios::sync_with_stdio(false);
	cout.tie(0);
	cin.tie(0);
	int n;
	cin >> n;
	if (n < 6)
	{
		puts("empty");
		return 0;
	}
	
	get_primes();
	ll sum = 1;
	while (sum * 6 <= n) sum *= 2;
	ll ans = sum;
	for (int i = 1; primes[i] * 2 <= n; ++i)
	{
		int p = primes[i];
		sum = 1;
		while (sum * p * 2 <= n) sum *= p;
		ans = ans * sum % mod;
	}
	cout << ans << endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值