牛客2025秋季算法编程训练联赛4-基础组

A

观察题意可以知道,递归0次时,\(a=1,b=0\);递归一次时\(a=2,b=1\)

那么递归2次呢?我们知道有\(\gcd(a,b)=\gcd(b,a\mod b)\),那么也就是\(\gcd(x,2)=\gcd(2,1)\)\(x\equiv 1\mod 2\),最小正整数\(x\)\(x>b\))为\(b+a\mod b\),所以我们就得到了递推式:

\[f_k(a+b,a)=\begin{cases}f_0(1,0)&k=0,\\f_1(2,1)&k=1,\\f_{k-1}(a,b)&k\geq2.\end{cases} \]

我们可以直接预处理每一项的值,然后每次\(O(1)\)回答。

B

典题,使用栈来模拟就好。

C

给出一个长度为 n 的数列 \(a_1,a_2,\ldots,a_n\),求其长度为 k 的连续子段的乘积对 998244353 取模余数的最大值。

\(1 \le k \le n \le 2*10^5\)

\(0 \le a_i <998244353\)

sol

因为\(a_i\)可能为0,所以我们按照0对\(a\)数组分成许多子数组,这个可以使用双指针来完成,接着我们只需要处理每一段不为0的子区间的最优解,最后求所有子区间的最大值就好。

处理每一段\(b\)

  • 如果长度小于\(k\),直接返回0
  • 否则可以使用滑动窗口来写,假设现在求得了\([l,l+k-1]\)的答案\(res_1\),现在要求\([l+1,l+k]\)的答案\(res_2\),我们可以\(O(\log{n})\)转移,\(res2=res_1\times b_l^{-1}\times b_{l+k}\)
  • 最后返回最大值。

D

典题,子区间和为\(k\)的子区间数量,可以通过前缀和+map来做。

这道题只需要换成前缀异或和就好。

E

给出一个包含数字1-9和加号的字符串,请你将字符串中的字符任意排列,但每种字符数目不变,使得结果是一个合法的表达式,而且表达式的值最小。输出那个最小表达式的值

合法的表达式的定义如下:

- 一个数字,如233,是一个合法的表达式

- A + B是合法的表达式,当且仅当 A , B 都是合法的表达式

保证给出的表达式经过重排,存在一个合法的解。

一行输入一个字符串,仅包含数字1-9和加号+。

字符串的长度小于\(5*10^5\)

sol

很显然我们得先把所有数拿出来,用\(num\)记录,同时统计加号的个数\(cnt\),那么我们需要把\(num\)分成\(cnt+1\)个数,一个很显然的贪心是这\(cnt+1\)个数的长度要尽可能接近,同时可以对\(num\)从小到大排序,对于每个数字\(num_i\),我们将其分到\(i\mod (cnt+1)\)那一组,最后做一个高精度加法就好。而且可以很容易这样构造的答案是最优的。

#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef  long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N =  + 9, M = 2e5 + 9, mod = 1e9 + 7;
int c[N];
void solve() {
	string s;
	cin >> s;
	string num;
	int cnt=0;
	for(char c:s){
		if(c=='+') cnt++;
		else num+=c;
	}
	sort(num.begin(),num.end());
	if(cnt==0){
		cout << num << endl;
		return;
	}
	vector<string> tmp(cnt+1);
	for(int i=0;i<num.size();i++){
		tmp[i%(cnt+1)]+=num[i];
	}
//	cout << tmp[0];
//	for(int i=1;i<=cnt;i++){
//		cout << "+" << tmp[i];
//	}
//	cout << endl;
	for(string t:tmp){
		reverse(t.begin(),t.end());
		for(int i=0;i<t.size();i++){
			c[i]+=t[i]-'0';
		}
	}
	string ans;
	int carry=0;
	int i=0;
	do{
		int t=c[i];
		c[i]=(c[i]+carry)%10;
		ans+=to_string(c[i]);
		carry=t/10;
		i++;
	}while(carry!=0||c[i]!=0);
	reverse(ans.begin(),ans.end());
	cout << ans << endl;
}
/*

*/
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int t = 1;
//	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

F

现有一个 n 个点,n-1条边组成的树,其中 1 号点为根节点。

牛牛和牛妹在树上玩游戏,他们在游戏开始时分别在树上两个不同的节点上。

在游戏的每一轮,牛牛先走一步,而后牛妹走一步。他们只能走到没有人的空节点上。如果谁移动不了,就输掉了游戏。现在牛牛和牛妹决定随机选择他们分别的起点,于是他们想知道,有多少种游戏开始的方式,使得牛牛存在一种一定获胜的最优策略。

两种开始方式相同,当且仅当在两种开始方式中牛牛,牛妹的开始位置是分别相同的,否则开始方式就被视作不同的。

sol

我们从简单入手,两人相距1时,可以发现先手必输;

两人相距为2时,先手可以向后手移动一步,相距变成1,后手为必输局面,先手必胜;

两人相距为3时,先手肯定不想变成2,那么只能变成4,而后手可以靠近先手,相距再次变为3,就这样一直走,直到先手在叶子节点,只能变为2,那么先手必输;

\(\dots\)

可以发现只有两人相距距离为偶数时,先手必胜,所以问题转化为求所有相距为偶数的点对,同时\((u,v)\ne(v,u)\)

主播赛时的做法为用树上dp来做,显然是可以的,不过表达式写的时候要注意一点:

\(dp[u][0/1]\)表示以\(u\)为一个端点,所有\(\sum_{v\in s_u\and \not u}{[dist_{u,v}\mod 2=0/1]}\)的个数,用一个\(dfs\)就可以统计。

统计完这个,我们还需要统计其向上有多少个奇数距离的点,多少偶数距离的点,所以我们还需要有一个\(up[u][0/1]\),直接看代码:

void solve() {
	int n;
	cin >> n;
	vector<vector<int>> tr(n+1);
	vector<int> p(n+1);
	for(int i=2;i<=n;i++){
		cin >> p[i];
		tr[p[i]].push_back(i);
		tr[i].push_back(p[i]);
	}
	vector<vector<int>> dp(n+1,vector<int>(2,0));
	auto dfs=[&](auto&& self,int u,int p)->void{
		for(int v:tr[u]){
			if(v==p) continue;
			self(self,v,u);
			dp[u][0]+=dp[v][1];
			dp[u][1]+=dp[v][0]+1;
		}
	};
	dfs(dfs,1,0);
	vector<vector<int>> up(n+1,vector<int>(2,0));
	for(int i=2;i<=n;i++){
		up[i][0]=up[p[i]][1]+(dp[p[i]][1]-dp[i][0]-1);
		up[i][1]=up[p[i]][0]+1+(dp[p[i]][0]-dp[i][1]);
	}
	int ans=dp[1][0];
	for(int i=2;i<=n;i++){
		ans+=dp[i][0]+up[i][0];
	}
	cout << ans << endl;
}

主播赛时\(up[i][1]\)的转移式写错了,忘记了加1,导致没过。

还有一个更好的做法:我们只要相距为偶数的点,那么我们一遍\(dfs\),统计每个节点的深度,然后统计偶数深度的节点数\(even\),奇数深度的节点\(odd\),那么对于两个奇偶性相同的节点\(u,v\),他们的距离是偶数,因为\(dist_{u,v}=depth_u+depth_v-2\times depth_{lca(u,v)}\)

所以最后答案为\(ans=2\times(C(odd,2)+C(even,2))\)

void solve() {
	int n;
	cin >> n;
	vector<int> depth(n+1);
	for(int i=2;i<=n;i++){
		int p;cin >> p;
		depth[i]=depth[p]+1;
	}
	vector<int> c(2,0);
	for(int i=1;i<=n;i++){
		c[depth[i]%2]++;
	}
	int ans=(c[0]-1)*c[0]/2+(c[1]-1)*c[1]/2;
	cout << ans*2 << endl;
}
posted @ 2025-11-06 15:52  alij  阅读(8)  评论(0)    收藏  举报