牛客寒假训练营第三场

B

题目描述:

智乃来到水果摊前买瓜,水果摊上贩卖着\(N\)个不同的西瓜,第\(i\)个西瓜的重量为\(w_{i}\)。智乃对于每个瓜都可以选择买一个整瓜或者把瓜劈开买半个瓜,半个瓜的重量为\(\frac{w_i}{2}\)。也就是说对于每个西瓜,智乃都有三种不同的决策:

1.购买一整个重量为\(w_{i}\)的西瓜;2.把瓜劈开,购买半个重量为\(\frac{w_{i}}{2}\)的西瓜;3.不景行购买操作。

为了简化题目,我们保证所有瓜的重量都是一个正偶数。现在智乃想要知道,如果它想要购买西瓜的重量和分别为\(k=1,2,3\dots M\)时,有多少种购买西瓜的方案,因为这些数字可能会很大,请输出方案数对\(10^{9}+7\)取余数后的结果。

分析:

​ 首先我们要求的是方案数,题目种给了买瓜的决策,要么不买西瓜,要么买一半的西瓜,要么买一整个西瓜,我们很容易可以想到01背包\(DP\)模型来进行求解,不妨开一个\(dp[N][M]\)的二维数组。

​ 每一次的状态都是由三个决策组成的,也就是当我们选到第\(i\)个物品的时候,此时的重量是\(j\),那么\(dp[i][j]\)表示的方案数就是由\(dp[i - 1][j]\)这个不选择这个当前瓜的方案加上\(dp[i - 1][j - w]\)买一整个瓜的决策再加上\(dp[i - 1][j - \frac{w}{2}]\)买半个瓜的决策构成的这样我们就推出来了很经典的01背包的状态转移方程\(dp[i][j] = dp[i - 1][j] + dp[i - 1][j - w] + dp[i - 1][j - \frac{w}{2}]\)。当然我们也可以用滚动数组来进行优化,从而少掉一维,最终的转移方程就是\(dp[j] = dp[j - w] + dp[j - \frac{w}{2}]\)

#include <bits/stdc++.h>

using i64 = long long;

constexpr int mod = 1e9 + 7;

int main() {
	std::cin.tie(nullptr)->sync_with_stdio(false);
	int n, m;
	std::cin >> n >> m;
	std::vector<int> dp(m + 1);
	dp[0] = 1;
	for (int i = 1; i <= n; i ++ ) {
		int x;
		std::cin >> x;
		for (int j = m; j >= x / 2; j -- ) {
			dp[j] = (dp[j] + dp[j - x / 2]) % mod;
			if (j >= x) dp[j] = (dp[j] + dp[j - x]) % mod;
		}
	}

	for (int i = 1; i <= m; i ++ ) std::cout << dp[i] << " \n"[i == m];
}

D

题目描述:

智乃有一个长度为\(N\)仅有字符\('0'\)\('1'\)组成的字符串,现在智乃想把这个01串打乱,变得和之前不一样。请你将这个01串打乱后输出。
打乱指的是,打乱后的01串和原本的01串中字符\('0'\)\('1'\)的数目相同,字符串长度相同,且至少存在一个位置上的字符不同。输入保证其中字符0和1至少会出现一次。

分析:

这个题的思路很简单,只要找到某一个0和1的位置然后将两个字符交换位置就好了,但是参考\(Jiangly\)大佬的代码,我们交换字符的时候不需要很朴素的借用中间变量来实现,可以用异或的性质来巧妙的实现,这样我们可以很快速的求出和\(s[0]\)不一样的另一个字符所在的位置,然后我们直接用\(swap\)函数来交换就可以了。

#include <bits/stdc++.h>
using i64 = long long;
int main() 
{
	std::string s;
	int n;
	std::cin >> n >> s;
	int x = s.find(s[0] ^ '0' ^ '1');
	std::swap(s[0], s[x]);
	std::cout << s << "\n";
}

I

题目描述:

智乃去注册账号,他发现网站的的密码必须符合以下几个条件:

  • 密码的长度不少于\(L\)个字符,并且不多于\(R\)个字符。

  • 密码是仅包含大小写英文字母、数字、特殊符号的字符串。

  • 密码中应该至少包括①大写英文字母、②小写英文字母、③数字、④特殊符号这四类字符中的三种。

    所谓特殊字符,是指非大小写字母、数字以及空格回车等不可见字符的可见字符。现在智乃有一个长度大小为\(N\)的字符串\(S\),她想知道\(S\)串中有多少个子串是一个符合条件的密码,请你帮助智乃统计符合条件的密码数目。子串是指字符串中某一段连续的区间,例如对于字符串"abcde"来说,"abc","cde"都是它的子串,而"ace"不是它的子串。

分析:

​ 因为我们要找的区间的长度是有规定且在一定范围内的,所以我们对于这种题总可以发现我们可以固定一个端点,然后再去找右端点的位置,这样很明显右端点是单调的,对于这种问题我们可以想到双指针算法来确定左右边界从而达到我们想要的结果。小技巧:在判断一个字符是大小写还是数字还是其他字符的时候可以用一些函数来帮我们实现,就比如\(std::isupprt、std::islower、std::isdigit\)来快速判断类型,可以帮我们节约一点写代码的时间。

​ 在双指针进行确定左右边界的时候我们一定要记住在滑动这个窗口的时候,要记得把更新之后加入到窗口的元素加进来,更新出去的元素剔除出去。

#include <bits/stdc++.h>

using i64 = long long;

void solve()
{
	int n, l, r;
	std::cin >> n >> l >> r;
	std::string s;
	std::cin >> s;
	std::vector<int> a(n + 1);
	for (int i = 0; i < n; i++ ) {
		if (std::isupper(s[i])) a[i] = 0;
		else if (std::islower(s[i])) a[i] = 1;
		else if (std::isdigit(s[i])) a[i] = 2;
		else a[i] = 3;
	}

	int cnt[4] = {};
	i64 res = 0;

	for (int i = 0, j = 0; i < n; i ++ ) {
		while (j <= n && (cnt[0] > 0) + (cnt[1] > 0) + (cnt[2] > 0) + (cnt[3] > 0) < 3) {
			if (j < n) cnt[a[j]] ++ ;
			j ++;
		}

		int L = std::max(l + i, j);
		int R = std::min(n, i + r);
		res += std::max(0, R - L + 1);

		cnt[a[i]] --;
	}

	std::cout << res << "\n";
}

int main() {
	std::cin.tie(nullptr)->sync_with_stdio(false);
	int T;
	T = 1;
	while (T--) solve();

	return 0;
}
posted @ 2022-04-13 14:58  浅渊  阅读(61)  评论(0)    收藏  举报