蓝桥杯C++A组真题练习(一)

蓝桥杯C++A组真题练习

  • 糖果
  • 重复字符串
  • 子串分值
  • 修改数组

糖果

蓝桥杯C++A组真题练习(一)_第1张图片
状压DP水题,没啥好说的。

#include
#include
using namespace std;
const int N = 110, M = 20, K = 1 << 20, INF = 0x3f3f3f3f;
int w[N], dp[K], n, m, k;
int read() {//快读板子
	char ch = getchar();
	int x = 0;
	while (ch < '0' || ch>'9') ch = getchar();
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x;
}
int main() {
	n = read(), m = read(), k = read();
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= k; j++)
			w[i] |= 1 << (read() - 1);//这里我们用二进制的0~m-1位表示1~m各种口味糖果的状态,更方便
	memset(dp, 0x3f, sizeof dp);//初始化为INF,便于求最小值
	dp[0] = 0;//购买0种口味最少需要买0包
	for (int i = 0; i < 1 << m; i++)//枚举所有状态
		for (int j = 1; j <= n; j++)//枚举每一包糖果
			dp[i | w[j]] = min(dp[i | w[j]], dp[i] + 1);
	if (dp[(1 << m) - 1] == INF) dp[(1 << m) - 1] = -1;//题目里是有不能全部品尝的情况的,注意特判
	cout << dp[(1 << m) - 1] << endl;
	return 0;
}

重复字符串

蓝桥杯C++A组真题练习(一)_第2张图片
很明显,我们要把一个字符串修改成k次字符串,改字符串长度必须是k的整数倍,否则肯定无法修改成功,而可以成功的情况下我们把字符串分成了k段,假设该字符串的长度为n,那么该重复字符串的重复部分的长度为n/k,我们只需要遍历每一段的相同位置,找出该相同位置出现次数最多的字母出现的次数,把剩下的字母全部改成该字母,即可保证答案最小。

#include
using namespace std;
string s;
int k, ans;
int main() {
	cin >> k >> s;
	if (s.size() % k) {
		cout << -1;
		return 0;
	}
	int len = s.size() / k;
	for (int i = 0; i < len; i++) {//考虑每个位置
		int cnt[26] = { 0 };
		for (int j = 0; j < k; j++)
			cnt[s[j * len + i] - 'a']++;
		int ma = 0;
		for (int i = 0; i < 26; i++)
			ma = max(ma, cnt[i]);
		ans += k - ma;
	}
	cout << ans << endl;
	return 0;
}

子串分值

蓝桥杯C++A组真题练习(一)_第3张图片
很典型的一道题,让我想起了单调栈(和这题没关系但又有关系)。
很容易想到的解法就是枚举所有区间,然后找出该区间中出现一次的字符个数,这样肯定是不能拿到满分的,因此我们考虑优化,我们依次考虑每一个字母,找出它在多少个区间内只出现了一次,就能统计出正确答案。至于怎么找出它在多少个区间内只出现一次,我们假设我们当前到了第i个位置,我们找到前一个和它相同字母的位置j还有后一个和它相同字母的位置k,则该位置上的字母满足的区间数为(i-j)*(k-i),很容易知道,尽管我们写了两层for循环,我们最多也只会遍历整个数组27遍,因此很容易通过此题。

#include
#include
using namespace std;
int last[26];//last[i]记录第i个字母对应的上一次出现的位置
//第0个字母即a,以此类推
char s[100010];
long long ans;
int main() {
	scanf("%s", s + 1);
	int n = strlen(s + 1);
	for (int i = 1; i <= n; i++) {
		int j = i + 1;
		while (j <= n && s[j] != s[i]) j++;//找到下一个等于该字母的位置
		ans += (long long)(j - i) * (i - last[s[i] - 'a']);//防止爆int
		last[s[i] - 'a'] = i;
	}
	cout << ans << endl;
	return 0;
}

修改数组

蓝桥杯C++A组真题练习(一)_第4张图片

呵呵,要是在考场上遇到这题,我可能就只能写个暴力了,想了许久没想出正解,就去搜题解,没想到这题居然能用并查集!
这篇题解就不错:蓝桥杯 修改数组[并查集]
可惜像我这种笨脑子是想不到这种解法的,不过,我们还有更容易想到的解法,首先,最坏情况下初始数组也就是105个106,因此我们用到的最大的数不会超过1100000,我们考虑维护一个权值树状数组,并用一个vis数组表示每个数是否出现过,然后遍历整个数组,如果这个数没出现过,该位置的答案不用再修改,我们更新权值树状数组并更新vis数组,如果该数(设为x)出现过,我们就需要找出一个边界mid,使得x ~ mid之间有数字没有出现过,也就是query(mid)-query(x-1)!=mid-x+1,可以可以很容易知道这个边界具有单调性,并且在x+1 ~ 1100000之间,这样我们就可以通过二分找到最小的这个边界,这个边界就是该位置的最终答案。时间复杂度O(n log2m),这里n是105级别,m是1.1e6级别,并且常数很小,是可以稳过的。

#include
using namespace std;
const int N = 1e6 + 1e5 + 10;
int tr[N], w[N], n;
bool vis[N];
int read() {
	char ch = getchar();
	int x = 0;
	while (ch < '0' || ch>'9') ch = getchar();
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x;
}
int lowbit(int x) {
	return x & -x;
}
void add(int x) {
	for (int i = x; i < N; i += lowbit(i))
		tr[i]++;
}
int query(int x) {
	int res = 0;
	for (int i = x; i; i -= lowbit(i))
		res += tr[i];
	return res;
}
bool check(int l, int r) {
	if (query(r) - query(l - 1) == r - l + 1) return false;
	return true;
}
int main() {
	n = read();
	for (int i = 1; i <= n; i++) {
		w[i] = read();
		if (!vis[w[i]]) {
			printf("%d ", w[i]);
			vis[w[i]] = 1;
			add(w[i]);
			continue;
		}
		int l = w[i] + 1, r = 1e6 + 1e5;
		while (l < r) {
			int mid = l + r >> 1;
			if (check(w[i], mid)) r = mid;
			else l = mid + 1;
		}
		vis[l] = 1;
		printf("%d ", l);
		add(l);
	}
	return 0;
}

唉,感觉自己还是太笨了,稍微有点思维难度的都写不出来。仍需努力。

你可能感兴趣的:(蓝桥杯,算法,蓝桥杯)