2020牛客寒假算法基础集训营4(2020.2.12)

比赛链接:https://ac.nowcoder.com/acm/contest/3005

  • A 欧几里得
  • B 括号序列
  • C 子段乘积
  • D 子段异或
  • E 最小表达式
  • F 树上博弈
  • G 音乐鉴赏
  • H 坐火车
  • I 匹配星星
  • J 二维跑步

A 欧几里得

解题思路:
我是找规律看出来的,一直试试试,试出了前几项,很容易猜到是一个斐波那契数列🤡(别打我)

a:1 b:0 t:0
a:2 b:1 t:1
a:3 b:2 t:2
a:5 b:3 t:3
a:8 b:5 t:4

提前求出 f i b o n a c c i fibonacci fibonacci 数列就好了,注意前几项特判,数据爆int,要开long long

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[100];
int main()
{
	a[0] = 2;
	a[1] = 3;
	for (int i = 2; i <= 80; i++) a[i] = a[i - 1] + a[i - 2];

	int T, n;
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d", &n);
		if (n == 0)
			printf("1\n");
		else
			printf("%lld\n", a[n]);
	}
	return 0;
}

B 括号序列

解题思路:
栈匹配括号的基础题。
遍历括号序列,遇到左括号进栈,遇到右括号匹配栈顶括号……
不是合法的括号序列:

  1. 右括号与栈顶不匹配
  2. 遇到右括号,栈为空
  3. 遍历完后栈不为空

Code:

#include <bits/stdc++.h>
using namespace std;
int main()
{
	string str;
	cin >> str;
	int len = str.length();
	stack<char> s;
	for (int i = 0; i < len; i++)
	{
		if (str[i] == '(' || str[i] == '[' || str[i] == '{')
		{
			s.push(str[i]);
		}
		else
		{
			if (s.empty())
			{
				cout << "No" << endl;
				return 0;
			}
			if ((s.top() == '(' && str[i] == ')') || (s.top() == '[' && str[i] == ']') || (s.top() == '{' && str[i] == '}'))
			{
				s.pop();
			}
			else
			{
				cout << "No" << endl;
				return 0;
			}
		}
	}
	if (s.empty())
		cout << "Yes" << endl;
	else
		cout << "No" << endl;
	return 0;
}

C 子段乘积

一开始想到的是用类似前缀和,求出了“前缀乘积”,然后 m u l [ l , r ] = m [ r ] / m [ l − 1 ] mul[l, r]=m[r]/m[l-1] mul[l,r]=m[r]/m[l1],这样涉及到乘法逆元,没关系,求乘积的时候顺便求出逆元就好了,然而还是不行,后来发现不能包含0,要按照所有的0分段,分别求每段的子段乘积取最大值,太麻烦了😖,所以又想到了线段树,把普通线段树的加操作改成乘就OK了,非常简单,build+query 搞定。

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int N = 200000 + 10;

ll mul[N << 2];
ll lazy[N << 2];

//改成乘法,取模
void push_up(int root) { mul[root] = mul[root << 1] * mul[root << 1 | 1] % mod; }

void buildTree(int l, int r, int root)
{
	lazy[root] = 0;
	if (l == r)
	{
		ll x;
		scanf("%lld", &x);
		mul[root] = x;
		return;
	}
	int mid = (l + r) >> 1;
	buildTree(l, mid, root << 1);
	buildTree(mid + 1, r, root << 1 | 1);
	push_up(root);
}

ll query(int L, int R, int l, int r, int root)
{
	if (L <= l && r <= R)
		return mul[root];

	int mid = (l + r) >> 1;
	ll ans = 1;
	if (L <= mid)
		ans = (ans * query(L, R, l, mid, root << 1)) % mod; //这里记得也要改成乘法,取模
	if (mid < R)
		ans = (ans * query(L, R, mid + 1, r, root << 1 | 1)) % mod; //乘法 取模
	return ans;
}

int main()
{
	int n, k;
	scanf("%d%d", &n, &k);

	buildTree(1, n, 1);

	ll ans = -1;
	for (int i = 1, j = k; j <= n; i++, j++)
		ans = max(ans, query(i, j, 1, n, 1));

	printf("%lld\n", ans);
	return 0;
}

D 子段异或

解题思路:
这个题困扰了我很久,dalao同学说循环+map😫???后来才想明白。
异或的性质: a a = 0 a^a=0 aa=0,也就是说一个数异或两次相当于没参与异或,这里敲重点。
这道题我们就可以这样做:用一个数t记录前 i i i 个数的异或,如果map[t]不为0,说明前面出现过异或和为t的段(假设是前 j j j 个数的异或为t),t(前 i i i个数) 异或 t(前 j j j个数) = 0,不就相当于前 j j j 个数没有参与异或,所以异或为0的段就是 [ j + 1 , r ] [j+1,r] [j+1,r]

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
map<ll, ll> m;
int main()
{
	int n;
	ll a;
	scanf("%d", &n);
	ll t = 0, cnt = 0;
	m[0] = 1; //初始值,对应区间[1,i]异或和为0时加1
	for (int i = 0; i < n; i++)
	{
		scanf("%lld", &a);
		t = t ^ a;
		cnt += m[t]; //如果map中没有t,返回0
		m[t]++;
	}
	printf("%lld\n", cnt);
	return 0;
}

E 最小表达式

本来以为需要写高精加,愣是没写出来,看了出题人的解法后,仿佛打开了通往新世界的大门🤩。
贪心。基本的思路是把大的数字放到权值小的位置,然后相加。然后做相加的时候就脑子短路了😑,想的是写出具体cnt个数,一个一个加。简单的方法是先把每一个权值位相加,再计算进位。因为每个权值位的cnt个数顺序无所谓
2020牛客寒假算法基础集训营4 - E
Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 500000 + 10;

char str[N];
int arr[11];
int sum[N];

int main()
{
	scanf("%s", str);
	int len = strlen(str);
	int cnt = 1; //加数的个数
	for (int i = 0; i < len; i++)
	{
		if (str[i] == '+') //多一个加号就多一个加数
			cnt++;
		else
			arr[str[i] - '0']++;
	}
	int j = 0, k = 0;
	//贪心,大的数字放在权值小的位置,cnt个数的权值小的位都放完了再往权值高为放
	for (int i = 9; i > 0; i--)
	{
		while (arr[i]) //数字i,的数量是arr[i]
		{
			sum[k] += i; //sum[]记每个权值(10^i)位的和,相当于把cnt个数的每一位先单独加起来,然后再计算进位
			arr[i]--;
			j = (j + 1) % cnt;
			if (!j)
				k++; //cnt个数的第k位都放上数了,再往高位放
		}
	}
	for (int i = 0; i < N - 1; i++) //计算每一位的进位
	{
		sum[i + 1] += sum[i] / 10;
		sum[i] %= 10;
	}
	for (j = N - 1; j >= 0 && sum[j] == 0; j--); //去前缀0

	for (; j >= 0; j--)
		printf("%d", sum[j]);
	printf("\n");

	return 0;
}


F 树上博弈

Code:



G 音乐鉴赏

Code:



H 坐火车

Code:



I 匹配星星

Code:



J 二维跑步

Code:



牛客题解

https://ac.nowcoder.com/discuss/365889

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值