牛客周赛 round 1

目录

1.游游画U

2.游游的数组染色

3.游游的交换字符

4.游游的9的倍数


1.游游画U

题目描述

游游想让你画一个大小为 nnn 的 "U" ,你能帮帮她吗?
具体的画法见样例说明。

输入描述:

一个正整数 nnn。
1≤n≤50

输出描述:

共输出 4n 行,每行输出一个长度为 4n 的,仅包含 '*' 和 '.' 两种字符的字符串。

示例1

输入

1

输出

*..*
*..*
*..*
.**.

示例2

输入

2

输出

**....**
**....**
**....**
**....**
**....**
**....**
.**..**.
..****..

示例3

输入

3

输出

***......***
***......***
***......***
***......***
***......***
***......***
***......***
***......***
***......***
.***....***.
..***..***..
...******...

思路

模拟,前3n行为:n个'*'+2n个'.'+n个'*'

后n行为:i从1到n遍历,每一行为:i个'.'+n个'*'+(2n-2i)个'.'+n个'*'+i个'.'

代码

#include <iostream>
#include <cstdio>
using namespace std;
int n;
int main()
{
	cin >> n; 
	for (int i = 0; i < 3 * n; i++) {
		for (int j = 0; j < 4 * n; j++) {
			if (j < n || j >= 3 * n) {
				cout << '*';
			}
			else {
				cout << '.';
			}
		}
		cout << endl;
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < i; j++) {
			cout << '.';
		}
		for (int j = 0; j < n; j++) {
			cout << '*';
		}
		for (int j = 0; j < 2 * n - 2 * i; j++) {
			cout << '.';
		}
		for (int j = 0; j < n; j++) {
			cout << '*';
		}
		for (int j = 0; j < i; j++) {
			cout << '.';
		}
		cout << endl;
	}
	return 0;
}

2.游游的数组染色

题目描述

游游拿到了一个数组,其中一些数被染成红色,一些数被染成蓝色。
游游想知道,取两个不同颜色的数,且它们的数值相等,有多少种不同的取法?
我们定义,两种取法如果取的某个数在原数组的位置不同,则定义为不同的取法。

输入描述:

第一行输入一个正整数 n,代表数组的长度。
第二行输入 n 个正整数 ai,代表数组中的元素。
第三行输入一个长度为 n ,仅包含 'R' 和 'B' 两种字符的字符串,第 i 个字符为 'R' 代表数组第 i个元素被染成红色,为 'B' 代表被染成蓝色。

输出描述:

输出一个整数,代表取数的方案数量。

示例1

输入

5
1 2 1 2 2
BRRBB

输出

3

说明

第一种取法,取第一个数和第三个数,颜色不同且都是1。

第二种取法,取第二个数和第四个数,颜色不同且都是2。

第三种取法,取第二个数和第五个数,颜色不同且都是2。

示例2

输入

3
2 3 3
BBB

输出

0

说明

所有数都是蓝色,显然取不出两个颜色不同的数。

思路:

利用map统计每一个数值对应的红色和蓝色的数量,遍历其中一个map容器,将出现的数值的蓝色数量和红色数量相乘,加到答案中

代码

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;
typedef long long ll;
ll n, ans;
ll a[500010];
string s;
map<int, int>p1;//蓝色
map<int, int>p2;//红色
int main()
{
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	for (int i = 0; i < 500010; i++) {
		p1[i] = 0;
		p2[i] = 0;
	}
	cin >> s;
	for (int i = 0; i < s.length(); i++) {
		if (s[i] == 'B') {
			p1[a[i]]++;
		}
		else if (s[i] == 'R') {
			p2[a[i]]++;
		}
	}
	for (auto p : p1) {
		ans += p.second * p2[p.first];
	}
	cout << ans << endl;
	return 0;
}

3.游游的交换字符

题目描述

游游拿到了一个01串(仅由字符'0'和字符'1'构成的字符串)。游游每次操作可以交换两个相邻的字符,例如,对于字符串"11001"而言,游游可以交换第二个字符和第三个字符变成"10101"。
游游希望最终字符串任意两个相邻的字符都不相同,她想知道最少需要多少操作次数?保证答案是有解的,即最终一定能形成任意两个相邻的字符都不相同的字符串。

输入描述:

一行仅由 '0' 、 '1' 组成的字符串,字符串长度 n 满足 2≤n≤200000

输出描述:

游游使得相邻两个字母不等的最少操作次数。

示例1

输入

11100

输出

3

说明

先交换第三个、第四个字符,得到字符串"11010"。
然后交换第二个、第三个字符,得到字符串"10110"。
最后交换第四个、第五个字符,得到字符串"10101"。
总共交换3次。

示例2

输入

01011

输出

2

说明

先交换前两个字符,得到字符串"10011"

然后交换第三个、第四个字符,得到字符串"10101"

思路

在读懂了题意之后,第一反应就是,对于不同的输入,他的最终结果是是怎样的。

那就要将字符串的长度分为奇数和偶数进行讨论。

n = 奇数的话,最终的结果是唯一的,因为要么1比0的个数多1,要么0比1的个数多1,所以最终的字符串一定是以数量多的那个字符先开始的。

n = 偶数的话,1和0的数量是一样的多,所以最终的字符串有两种可能,一种是1开头,一种是0开头,但是这里1开头和0开头的构造方法和上面的是一样的。

知道了最终字符串的样子,就应该考虑,当前的字符串变成最终字符串的代价是多少。

题目中给的要求是,每次可以交换相邻的两个字符,那这样看来,仔细思索一下就会发现,字符串从 i 位置移动到 j 位置的代价就是abs(i-j),因为你只能两个两个进行交换。

然后我们再贪心的想一下,在你遍历之前你就已经知道字符串的长度n了,也就是说你已经知道最终的字符串是什么样子的了,至少n是奇数你是一定知道他最终样子的,n是偶数的话你只需要1开头和0开头都跑一遍取个代价最小的,也可以认为是知道最终的样子的。

当你从头遍历的时候,你已经知道字符串的最终样子,换句话说,你知道每个1的最终位置和0的最终位置,当所有的1都放在最终位置的时候,0也是在最终位置,所以你只看一个就够了。

以0开头为例,用 j 记录每个0应该放的位置,初始 j = 0.从头遍历 s ,当遇到s[i] = 0 的时候,sum+=abs(i-j),也就是当前 0 放到最近的他的最终位置的代价,然后 j += 2,j 向后移动两个位置。

这样贪心的理由是,每一个0都移动到离他最近的0的最终位置,每个0的移动代价都最小,最终代价和也是最小的。

代码

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;
string s;
ll cnt0, cnt1, sum1, sum2, j;//cnt表示0和1的个数
int main()
{
	cin >> s;
	int len = s.length();
	for (int i = 0; i < len; i++) {
		if (s[i] == '0') {
			cnt0++;
		}
		else if (s[i] == '1') {
			cnt1++;
		}
	}
	for (int i = 0; i < len; i++) {
		if (s[i] == '0') {
			sum1 += abs(i - j);
			j += 2;
		}
	}
	j = 0;
	for (int i = 0; i < len; i++) {
		if (s[i] == '1') {
			sum2 += abs(i - j);
			j += 2;
		}
	}
	if (cnt0 == cnt1 + 1) {
		cout << sum1 << endl;
	}
	else if (cnt1 == cnt0 + 1) {
		cout << sum2 << endl;
	}
	else {
		cout << min(sum1, sum2) << endl;
	}
	return 0;
}

4.游游的9的倍数

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

题目描述

游游拿到了一个数字串,她想取一个该数字串的子序列(子序列在原串中可以不连续),使得该子序列是9的倍数。子序列可以包含前导零。
游游想知道,一共能取多少个合法的子序列?答案请对 10^9+7 取模。
我们定义,若两个子序列在原串中的位置不同,则认为它们不同。

输入描述:

一个长度不超过200000的,仅由'0'~'9' 十种字符组成的字符串。

输出描述:

子序列是9的倍数的数量。答案请对 109+710^9+7109+7 取模。

示例1

输入

1188

输出

5

说明

共可以取4个不同的"18"子序列,和一个"1188"子序列,都是9的倍数。

示例2

输入

0123

输出

1

说明

只有子序列"0"是9的倍数。

思路

首先明确一点,一个数字如果是 9 的倍数的话,那么这个数字的所有数位之和是9的倍数。

ok,明确了这一点后,除法问题就变成了加法问题,也就是说从头开始走,当某段数字和是 9 的倍数的时候,这个数字就能是9的倍数,那么答案也就++。

那这样的话,就会发现,到某个位置之后,到该位置结尾的序列一共有9总可能,即除以9之后余数是1 ~ 9,到下一个位置的时候就可以通过前一个位置9种余数的情况转移到当前状态上来。

具体来讲,我们先从头遍历一遍字符串,记录到每一个位置的时候以当前位置为结尾余数为 j 的数量,j是1~9,0和9是一样的,所以算在一起。

然后从头开始遍历,i = 1 到 i = n ,a[i] 就是第 i 个位置的数字。 这时候我们定义一个 j 的循环,j 从 0 到 8 , 因为余数有9种可能。 dp[i][j]表示的意义为 i 位置及之前余数是 j 的数的数量,那状态转移方程就可以写为:

dp[i][(j+a[i])%9] = (dp[i][(j+a[i])%9] + dp[i-1][j] + dp[i-1][(j+a[i])%9])%M;

dp[i][(j+a[i])%9]:i 位置及之前余数是 (j+a[i])%9 的数量。
它可以由什么得来呢?
首先是前一个位置余数是 (j+a[i])%9 的数量,也就是dp[i-1][(j+a[i])%9],也就是说不算当前 i 位置的数,就已经有这些数量了。

其次就是由当前的这个数转移而来,那当前这个数和 j 相加之后,余数就是 (j+a[i])%9 , 那数量是多少呢?是当前 i 位置余数是 (j+a[i])%9 的原本的值 加上 前一个位置余数是 j 的值。

可能不好理解?我给你举个例子,就第一个1188的例子

dp[4][1] = dp[3][1] + dp[4][1] + dp[3][2]

这个状态转移方程看看各位萌新大佬能理解不?

dp[4][1]表示的是i=4的时候,余数是1的结果的数量。
那她可以由什么转移而来呢!?

可以是dp[3][1],也就是前一个位置余数是 1 的值.

再加上当前位置余数是1的原本的值dp[4][1]加上前一个位置余数是2的值。为什么这里余数是2呢?

你这么看肯定是看不懂的啊,你得看dp[4][1]里面的 1 是怎么来的,是a[i]+j 得到的,也就是(8+2)%9=1。看到这有没有一种恍然大悟的感觉,因为dp[4][1]里面的1是两部分组成,一个是a[i],一个是 j ,所以要想得到
dp[i][(j+a[i])%9],就应该是当前位置余数是(j+a[i])%9的数量加上当前位置之前的余数是 j 的数量,这样才是总数量。

不理解的话再多读几遍,然后试着写一下 dp[4][2],你就会真正理解其中的原委,答案我偷偷放在下面了哦

dp[4][2] = dp[3][2] + dp[4][2] + dp[3][3]

代码

#include <iostream>
#include <cstdio>
using namespace std;
const int M = 1000000007;
const int N = 200005;
string s;
long long int a[N], dp[N][10];
int main()
{
	cin >> s;
	int len = s.length();
	for (int i = 0; i < len; i++) {
		int k = s[i] - '0';
		a[i + 1] = k;
		dp[i + 1][k % 9]++;
	}
	int n = len;
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j <= 8; j++) {
			dp[i][(j + a[i]) % 9] += (dp[i - 1][j] + dp[i - 1][(j + a[i]) % 9]) % M;
		}
	}
	cout << dp[n][0] % M << endl;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JalenG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值