Ytu第三届Acm校赛部分题解

刚刚打完了校赛,趁着今晚有空,写一下部分题解吧。
A:运动会
B:有几个ACM
C:小张的困惑
D:超哥学回文
E:健忘的超哥
F:搬砖的小张要秃头
G:圣诞爷爷的礼物
H:套娃查询
I:天气の子
J:大乌龟冲呀!

A题暂时还没想出来怎么做,以后有时间再做吧。

B题:有几个Acm
在这里插入图片描述
我们考虑:对于字符C,开数组统计有多少个A在C前面,有多少M在C后面,然后两者相乘,进行累加即可,最后不要忘记取模。
Ac代码

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e5 + 7;
const int mod = 1e9 + 7;

string s;
int A[maxn], M[maxn], cnt, ans;

int main(void)
{
	cin >> s;
	int len = s.size();
	for(int i = 0; i < len; i ++ ) {
		if(s[i] == 'A') cnt ++;
		else if(s[i] == 'C') A[i] = cnt;
	}
	cnt = 0;
	for(int i = len - 1; i >= 0; i -- ) {
		if(s[i] == 'M') cnt ++;
		else if(s[i] == 'C') M[i] = cnt;
	}
	for(int i = 0; i < len; i ++ ) {
		if(s[i] == 'C') ans = (ans + A[i] * M[i]) % mod;
	}
	cout << ans;
	return 0;
}

C题:小张的困惑
在这里插入图片描述
在这里插入图片描述
这题貌似一开始数据有问题,就导致我们花费了一些时间在找错上,后来数据改过来之后,直接过。

因为这道题给的数比较少,所以我们直接考虑暴力搜索,取最小的一个答案即可。

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e5 + 7;

int num[maxn], n, ans = INT_MAX, tot;

void dfs(int start, int sum, int tar)
{
	if(sum >= tar) {
		if(ans > sum) ans = sum;
		return ;
	}
	for(int i = start; i < n; i ++ ) dfs(i + 1, sum + num[i], tar);
}

int main(void)
{
	int tar;
	cin >> n >> tar;
	for(int i = 0; i < n; i ++ ) cin >> num[i];
	dfs(0, 0, tar); // dfs(开始搜索的下标, 已经组合的数的和, 目标值)
	cout << ans;
	return 0;
}

D题:超哥学回文
在这里插入图片描述

对于这道题,我们可以考虑对于每一串进行分类讨论,首先判断A和B本身是不是回文,都不是回文的话,我们判断A和B,A从头开始,B从尾开始,在不相同的地方判断A和A的后半部分是不是回文。对于B同理。

Ac代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e5 + 7;

int main(void)
{
	string s1, s2;
	cin >> s1 >> s2;
	int len = s1.size(), l, r;
	l = 0, r = len - 1;
	while(l < r) {
		if(s1[l] != s1[r]) break;
		l ++, r --;
	}
	if(l >= r) {
		cout << "true";	// 判断A是不是回文
		return 0;
	}
	l = 0, r = len - 1;
	while(l < r) {
		if(s2[l] != s2[r]) break;
		l ++, r --;
	}
	if(l >= r) {
		cout << "true"; // 判断B是不是回文
		return 0;
	}
	l = 0, r = len - 1;
	while(l < r) {
		if(s1[l] != s2[r]) break;
		l ++, r --;
	}
	if(l >= r) {
		cout << "true";	// 判断A的前部分和B的后部分是不是回文
		return 0;
	}
	while(l < r) {
		if(s1[l] != s1[r]) break;	
		l ++, r --;
	}
	if(l >= r) {
		cout << "true"; // 判断A的前部分和A的后部分是不是回文
		return 0;
	}
	l = 0, r = len - 1;
	while(l < r) {
		if(s2[l] != s1[r]) break; 
		l ++, r --;
	}
	if(l >= r) {
		cout << "true"; // 判断B的前部分和A的后部分是不是回文
		return 0;
	}
	while(l < r) {
		if(s2[l] != s2[r]) break;
		l ++, r --;
	}
	if(l >= r) {
		cout << "true"; // 判断B的前部分和B的后部分是不是回文
		return 0;
	}
	cout << "false"; // 都不是则找不到这样的位置
	return 0;
}

E题:健忘的超哥
在这里插入图片描述
在这里插入图片描述
嗯… 这题其实有一个地方是错误的:就是输入的最后一句话,其实T的长度应该是不超过250000,好像是少写了一个0(后来我去问了肖老师,肖老师告诉我在比赛的时候这题其实就有问题,但是当时并没有修改题面)。

这题我们考虑:二分+字符串哈希。
这题我取的base是131,求出来131的1-250000的幂,然后将T串进行字符串哈希。我们在Si串中找出最短的串作为二分的右边界,接着再将每一个Si进行字符串哈希。 然后二分答案,对于二分的长度mid,求出Si中所有长度为mid的子串,用map进行存储。然后扫描T串,看是否能够求出n个满足条件的串,可以则将左边界调大,不可以则将右边界调小。

Ac代码:

#include <bits/stdc++.h>

using namespace std;
typedef unsigned long long ull;
const int maxn = 250100;
const int N = 25;

int lt, b = 131, n;
int len[N];
ull ht[maxn], hs[N][maxn];
char ch[N][maxn];
ull bit[maxn];

char str[maxn];

// 求Si满足长度为mid的子串的哈希值。
ull turn(int id, int l, int r) {
	return hs[id][r] - hs[id][l - 1] * bit[r - l + 1];
}
// 求T串中满足长度为mid的子串的哈希值
ull turns(int l, int r) {
	return ht[r] - ht[l - 1] * bit[r - l + 1];
}

bool Check(int k)
{
	map<ull, int> h;
	for(int i = 1; i <= n; i ++ ) {
		for(int j = k; j <= len[i]; j ++ ) {
			// 求所有Si中满足长度为k的子串的哈希值,用map存储
			h[turn(i, j - k + 1, j)] |= (1 << (i - 1));
		}
	}
	int pos = k, now = 1;
	while(pos <= lt) {
		if(h[turns(pos - k + 1, pos)] & (1 << (now - 1))) {
			now ++;
			if(now > n) return true;
			pos += k;
		}else pos ++;
	}
	return false;
}

int main(void)
{
	bit[0] = 1;
	for(int i = 1; i <= 250000; i ++ ) bit[i] = bit[i - 1] * b;
	scanf("%s", (str + 1));
	lt = strlen(str + 1);
	// 将T串进行哈希
	for(int i = 1; i <= lt; i ++ ) ht[i] = ht[i - 1] * b + (str[i] - 'a' + 1);
	scanf("%d", &n);
	int l = 1, r = maxn, ans = 0;
	for(int i = 1; i <= n; i ++ ) {
		scanf("%s", (ch[i] + 1));
		len[i] = strlen(ch[i] + 1);
		for(int j = 1; j <= len[i]; j ++ ) {
			// 将Si进行哈希
			hs[i][j] = hs[i][j - 1] * b + (ch[i][j] - 'a' + 1);
		}
		if(len[i] < r) r = len[i];
	}
	while(l <= r) {
		int mid = l + r >> 1;
		if(Check(mid)) ans = mid, l = mid + 1;
		else r = mid - 1;
	}
	if(ans) printf("%d", ans);
	else printf("-1");
	return 0;
}

F题:搬砖的小张要秃头

在这里插入图片描述
在这里插入图片描述

这是一道签到题,直接排序,然后从大的数开始往后枚举,直到满足题目要求。

Ac代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e5 + 7;
const int mod = 1e9 + 7;

string s;
int ans, n, num[maxn], tar, sum;

int main(void)
{
	cin >> n >> tar;
	for(int i = 1; i <= n; i ++ ) {
		cin >> num[i];
	}
	sort(num + 1, num + 1 + n);
	for(int i = n; i >= 1; i -- ) {
		if(sum >= tar) break;
		sum += num[i]; ans ++;
	}
	cout << ans;
	return 0;
}

G题:圣诞爷爷的礼物
在这里插入图片描述
在这里插入图片描述
这道题需要细心一点,需要考虑当n = 1的情况,这是不符合题意的,当时做的时候没有考虑到,直接Wa。剩下的情况就是先进行排序,然后分情况进行讨论。

Ac代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e5 + 7;

int n, m, a, b, num[maxn];

int main(void)
{
	cin >> n >> m >> a >> b;
	if(a < b) swap(a, b);	// 这里a是最大的, b是最小的
	for(int i = 1; i <= m; i ++ ) {
		cin >> num[i];
	}
	sort(num + 1, num + 1 + m);
	bool fa = false, fb = false;
	if(n == 1) cout << "NO";
	else {
		// 最小的比b还要小或者最大的比a还要大是不符合情况的。
		if(num[1] < b || num[m] > a) {
			cout << "NO" << endl;
		}else{
			if(num[1] == b) fb = true;
			if(num[m] == a) fa = true;
			// 如果都符合直接输出YES
			if(fa && fb) cout << "YES";
			else if(fa || fb) {
				// 如果两个当中有一个符合条件,需要判断n - m是不是大于等于1
				if(n - m >= 1) cout << "YES";
				else cout << "NO";
			}else if(!fa && !fb) {
				// 如果两个都不符合条件,需要判断n - m是不是大于等于2
				if(n - m >= 2) cout << "YES";
				else cout << "NO";
			}
		}
	}
	return 0;
}

H题:套娃查询
在这里插入图片描述

在这里插入图片描述

这题我们需要先处理出来1-1e6之间的每一个数i的g(i), 然后开二维数组记录每一个位置1-9之间的数有多少,最后O(1)查询。

Ac代码

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;

int num[maxn], cnt[15];
int dp[maxn][12];

int main(void)
{
	for(int i = 1; i <= maxn - 7; i ++ ) {
		int tmp = i;
		while(1) {
			if(tmp < 10) {
				num[i] = tmp;
				break;
			}
			int sum = 1;
			while(tmp) {
				int y = tmp % 10;
				if(y) sum *= y;
				tmp /= 10;
			}
			tmp = sum;
		}
	}
	for(int i = 1; i <= 1000000; i ++ ) {
		cnt[num[i]] ++;
//		dp[i][num[i]] = cnt[num[i]];
		for(int j = 1; j <= 9; j ++ ) dp[i][j] = cnt[j];
	}
	int n, l, r, k;
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++ ) {
		scanf("%d %d %d", &l, &r, &k);
		printf("%d\n", dp[r][k] - dp[l - 1][k]);
	}
	return 0;
}

I:天气の子
在这里插入图片描述
在这里插入图片描述
这题我们考虑维护一个单调递减栈,for循环每一个数,如果当前栈是空的或者当前元素比栈顶元素小,直接将当前数的下标压栈,如果当前的元素比栈顶元素大,则将栈顶元素出栈,并且取栈顶元素对应的数和当前数较小的数,并求出宽度。

Ac代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e5 + 7;

int h[maxn], n,ans;
stack<int> stk;

int main(void)
{
	cin >> n;
	for(int i = 1; i <= n; i ++ ) cin >> h[i];
	for(int i = 1; i <= n; i ++ ) {
		while(stk.size() && h[i] > h[stk.top()]) {
			int top = stk.top(); stk.pop();
			if(stk.empty()) break;	// 如果取出元素后栈为空,说明当前元素的左边已经没有墙了,直接break掉即可。
			int dis = i - stk.top() - 1;
			ans += (min(h[i], h[stk.top()]) - h[top]) * dis;
		}
		stk.push(i);
	}
	cout << ans;
	return 0;
}

J题:大乌龟冲呀!
在这里插入图片描述
在这里插入图片描述
题面需要花费 x, y , z分钟, 不是秒。

我们考虑维护dp[i][j]:所对应的意思就是将a串的前i个字符变为b的前j个字符需要花费的最小的时间。
插入操作: dp[i][j] = dp[i][j - 1] +x, 意思就是在如果a字符串需要插入一个字符才能和b的前j个字符相等,那么a插入的字符一定是b的第j个字符,所以插入之前是a的前i个字符和b的前j - 1个字符相等。
删除操作 dp[i][j] = dp[i - 1][j] + y, 意思就是如果a字符串需要删除一个字符才能和b的钱j的字符相等,那么说明在删除之前a的第i - 1个字符已经和b的第j个字符相等了。
替换操作 dp[i][j] = dp[i -1][j - 1] + (a[i - 1] == b[j - 1] ? 0 : z)需要考虑两种情况,如果a的第i个字符和b的第j个字符相同(这里我的下标是从1开始的),则不许要替换操作,如果不相等,则说明替换之间a 的 i - 1个字符和b的 j -1个字符是相等的。

然后三种操作取一个花费时间最小的就是当前操作需要花费的时间。

这题我们不可以直接开6000 * 6000的二维数组,否会爆内存的。所以我们考虑用滚动数组。

Ac代码::

#include <bits/stdc++.h>

using namespace std;
const int maxn = 6007;

int dp[2][6010];


int main(void)
{
	int x, y, z, t, cur, pre;
	string a, b;
	cin >> a >> b;
	cin >> x >> y >> z >> t;
	int n = a.size(), m = b.size();
	// 将a的前0个字符变为b的钱i个字符需要插入插座 时间为 i * x 
	for(int i = 0; i <= m; i ++ ) dp[0][i] = i * x;
	for(int i = 1; i <= n; i ++ ) {
		// 滚动下标
		cur = i % 2; 	// 当前下标
		pre = 1 - cur; // 前一个下标
        dp[cur][0] = i * y; // a的前i 个字符变为b的前0个字符需要删除操作, 时间为 i * y
		for(int j = 1; j <= m; j ++ ) {
			int left = dp[cur][j - 1] + x;
			int down = dp[pre][j] + y;
			int l_d = dp[pre][j - 1];
			// 这里我下标是从1开始的, 所以要减1
			if(a[i - 1] != b[j - 1]) l_d += z;
			dp[cur][j] = min(left, min(down, l_d));
		}
	}
	if(dp[n%2][m] <= t) cout << "Yes ";
	else cout << "No ";
	cout << abs(dp[n%2][m] - t);
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值