蓝桥杯每日练习(字符串哈希,最长公共子序列详解)

蓝桥杯冲冲冲~

回顾
🔖蓝桥杯每日练习(算式900,谈判幸运数,123)


🔖蓝桥杯每日练习(神奇算式,缩位求和,积木大赛)

🔖蓝桥杯每日练习(猴子分香蕉,等差数列,平方序列,倍数问题)


🔖蓝桥杯每日练习(纯质数(筛法的应用),最少砝码,灌溉)


🔖蓝桥杯每日练习(年龄巧合,纸牌三角形,取球游戏)


🚶引言

今天继续蓝桥杯每日练习~ 今天的题目最主要可以练习的呢,就是最长公共子序列模型和字符串哈希。字符串哈希是一个很常用的工具。虽然他单独被用来做考题的概率很小,但是他是一个工具,被一道题当作一个中间步骤来用的可能性是很大的。最长公共子序列是一个常见模型,换句话说,你掌握了这个模型就相当于会了很多题目。我是学习算法的小菜鸡,每天练习几个知识点,我们国赛见~

🚀今日题目

💓金币

模拟
题目描述

国王将金币作为工资,发放给忠诚的骑士。第一天,骑士收到一枚金币;之后两天(第二天和第三天),每天收到两枚金币;之后三天(第四、五、六天),每天收到三枚金币;之后四天(第七、八、九、十天),每天收到四枚金币…;这种工资发放模式会一直这样延续下去:当连续 N 天每天收到 N 枚金币后,骑士会在之后的连续 N+1 天里,每天收到 N+1 枚金币。

请计算在前 K 天里,骑士一共获得了多少金币。

原题传送门

解题报告

才1e4的数据那你还在想什么?模拟呀!依据题意循环就可以了。

参考代码(C++版本)
#include<iostream>

using namespace std;

int main() {
	int k;
	cin >> k;
	int flag = 1, ans = 0;
	while (k) {
		for (int i = 0; i < flag; i++) {
			ans += flag,k--;
			if (!k) break;
		}
		flag++;
	}
	cout << ans << endl;
}

🌟优秀的拆分

🌱题目描述

一般来说,一个正整数可以拆分成若干个正整数的和。

例如,1=1,10=1+2+3+4 等。对于正整数 n 的一种特定拆分,我们称它为“优秀的”,当且仅当在这种拆分下,n 被分解为了若干个不同的 2 的正整数次幂。注意,一个数 x 能被表示成 2 的正整数次幂,当且仅当 x 能通过正整数个 2 相乘在一起得到。

例如, 10 = 8 + 2 = 2 3 + 2 1 10=8+2=2^3+2^1 10=8+2=23+21

是一个优秀的拆分。但是, 7 = 4 + 2 + 1 = 2 2 + 2 1 + 2 0 7=4+2+1=2^2+2^1+2^0 7=4+2+1=22+21+20
就不是一个优秀的拆分,因为 1 不是 2 的正整数次幂。

现在,给定正整数 n,你需要判断这个数的所有拆分中,是否存在优秀的拆分。若存在,请你给出具体的拆分方案。
原题传送门

二进制拆分
🌴解题报告

因为拆分的方式的二的幂,所以我们自然的想到了二进制。考虑倒是二的正整数次幂,换句话说就是不可以是 2 0 2^0 20,所以奇数一定是无解的,因为奇数拆分成二的幂以后一定包含一个 2 0 2^0 20。然后对于偶数,我们就二进制枚举一下,二进制位上是1就输出2的位数次方。

然后我们在二进制拆分的时候可以用一下lowbit,lowbit(x) = x & -x,他可以返回二进制表示最后一个1以及它后面的0,也就是拆分成二进制以后的最小数。感觉这个东西还是比较重要的,因为理解lowbit函数是学习树状数组的基础。

🌵参考代码(C++版本)
#include<iostream>
#include<vector>

using namespace std;

int n;
vector<int>num;

int main() {
	cin >> n;
	if (n & 1) cout << -1 << endl;
	else {
		while (n) {
			num.push_back(n & -n);
			n -= n & -n;
		}
		for (int i = num.size() - 1; i >= 0; i--) cout << num[i] << ' ';
	}
}

穿越雷区

题目描述

X星的坦克战车很奇怪,它必须交替地穿越正能量辐射区和负能量辐射区才能保持正常运转,否则将报废。

某坦克需要从A区到B区去(A,B区本身是安全区,没有正能量或负能量特征),怎样走才能路径最短?

已知的地图是一个方阵,上面用字母标出了A,B区,其它区都标了正号或负号分别表示正负能量辐射区。

例如:

A + - + -
  
- + - - +
  
- + + + -
  
+ - + - +
  
B + - + -
  

坦克车只能水平或垂直方向上移动到相邻的区。

原题传送门

bfs
🚗解题报告

求的是最短路,我们第一反应是bfs。这道题有一个限制,就是说相邻两步的能量状态不能一样,所以加一个特判就是说不相等才能转移就可以了。

参考代码(C++版本)
#include<iostream>
#include<queue>
#include<cstring>

using namespace std;

typedef pair<int, int>PII;

const int N = 110;
char mp[N][N];
int g[N][N], n, b[N][N];
PII st, ed;

int dx[] = { 1,0,-1,0 };
int dy[] = { 0,1,0,-1 };

void find() {
	queue<PII>qu;
	memset(g, 0x3f, sizeof g);
	g[st.first][st.second] = 0;
	b[st.first][st.second] = 1;
	qu.push(st);
	while (qu.size()) {
		PII v = qu.front();
		qu.pop();
		for (int i = 0; i < 4; i++) {
			int x = dx[i] + v.first;
			int y = dy[i] + v.second;
			if (x >= 0 && x < n && y >= 0 && y < n && mp[x][y] != mp[v.first][v.second] && !b[x][y]) {
				g[x][y] = min(g[x][y], g[v.first][v.second] + 1);
				b[x][y] = 1;
				qu.push({ x,y });
			}
		}
	}
}

int main() {
	cin >> n;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			cin >> mp[i][j];
			if (mp[i][j] == 'A') st = { i,j };
			if (mp[i][j] == 'B') ed = { i,j };
		}
	}

	find();

	cout << g[ed.first][ed.second] << endl;

}

🙵蓝肽子序列

题目描述

L 星球上的生物由蛋蓝质组成,每一种蛋蓝质由一类称为蓝肽的物资首尾连接成一条长链后折叠而成。

生物学家小乔正在研究 L 星球上的蛋蓝质。她拿到两个蛋蓝质的蓝肽序列,想通过这两条蓝肽序列的共同特点来分析两种蛋蓝质的相似性。

具体的,一个蓝肽可以使用 1 至 5 个英文字母表示,其中第一个字母大写,后面的字母小写。一个蛋蓝质的蓝肽序列可以用蓝肽的表示顺序拼接而成。

在一条蓝肽序列中,如果选取其中的一些位置,把这些位置的蓝肽取出,并按照它们在原序列中的位置摆放,则称为这条蓝肽的一个子序列。蓝肽的子序列不一定在原序列中是连续的,中间可能间隔着一些未被取出的蓝肽。

如果第一条蓝肽序列可以取出一个子序列与第二条蓝肽序列中取出的某个子序列相等,则称为一个公共蓝肽子序列。

给定两条蓝肽序列,找出他们最长的那个公共蓝肽子序列的长度。

原题传送门

哈希与动态规划
🚇解题报告

题目废话有点多哈。题目说了个什么事儿呢?就是说一个以大写字母大头并且后面全是小写字母的子串称为一个肽,一个肽当作是一个元素,求在顺序保持不变的情况下最多能从两个字符串中同时选出多少个相同的肽。

读完题我们知道要把一个肽链看作是一个元素,所以我们很自然的想到了把字符串映射成一个整数。那么用什么方法呢?我们最常用的当然就是字符串哈希的方法了。字符串哈希是什么我们待会儿说,现在只需要知道字符串哈希以后可以把肽链(子字符串)映射成整数然后存在一个数组里。

然后呢?然后就是说我们要在两个整数数组中找到最长的公共子序列。这样就是我们很熟悉的最长公共子序列的问题了(不熟悉也没关系后面有说明)。

🙇字符串哈希

字符串哈希

就是一种把字符串映射成整数的算法。

  1. 首先我们有一种很自然的思路:所有数字的ascll码值加起来,就是一种映射方式。但是呢这样有一个问题:字符都一样但是顺序不一样比如ab和ba加起来不就是一样的数字吗?所以第一种方案否决掉了。
  2. 改进一下,我们要想一个办法让一个相同字符出现在字符串的不同位置对应的数字是不一样的,我们能想到这样一个办法:用不同的幂次来表示不同的位置。哎?这和x进制数在十进制下的意义好像啊。没错,我们就是可以用一个数字P来表示进制数,把字符串搞成P进制整数。那么P该是多少呢?为了防止冲突(冲突就是说不同的字符串映射以后的值是相同的),我们一般用大于字符串中字符种类数的数字就可以了。最常用的P是131和13331,如果字符串比较复杂,直接使用这两个数字一般也不会有冲突。
  3. 第二种思路似乎已经很完善了,但是呢字符串如果比较长P的增长可是指数级的,溢出的风险是很大的。那么我们比较直观的一种想法是给这个数字对MOD取余,这样可以保证不会溢出。在这里我们一般采用自然溢出的方式就是说给整数定义为无符号整数unsigned long long,它可以自动在数字溢出的时候将其对 2 64 2^{64} 264取余。
    所以得到公式:hash=s[0] * p 0 p^0 p0 + s[1] * p 1 p^1 p1 + s[2] * p 3 p^3 p3+…
🔗最长公共子序列

这个问题是一个明显要用到dp来解决的一个问题。

在这里插入图片描述

d p [ i ] [ j ] dp[i][j] dp[i][j]表示在第一个字符串的 i i i位以前和第二个字符串的 j j j位以前最长公共子序列的长度。我们思考方式呢就是看最后一位是否相同。如果相同,那么直接用前一个状态加一就可以了。如果不同呢我们就可以分以下是谁不在最长公共子序列里面。但是我们来向一个问题: f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]真的是不包含 a [ i ] a[i] a[i]包含 b [ j ] b[j] b[j]的最长公共子序列吗? f [ i − 1 ] [ j ] f[i−1][j] f[i1][j] 表示的是在 a a a的前 i − 1 i-1 i1个字母中出现,并且在b的前j个字母中的最长公共子序列,此时 b [ j ] b[j] b[j]不一定出现。但是我们仍可以用 f [ i − 1 ] [ j ] f[i−1][j] f[i1][j]来表示,原因就在于条件给定的情况被包含在 f [ i − 1 ] [ j ] f[i−1][j] f[i1][j]中,即条件的情况是 f [ i − 1 ] [ j ] f[i−1][j] f[i1][j]子集,而求的是 m a x max max,所以对结果不影响。

参考代码(C++版本)
#include<iostream>
#include<string>

using namespace std;

typedef unsigned long long ULL;

const int N = 1010, P = 13331;
int dp[N][N], idx_a, idx_b;
string ch1, ch2;
ULL a[N], b[N], res;

int id(char c) {
    return c - 'A' + 1;
}

void init() {
    ch1 += ' ', ch2 += ' ';
    for (int i = 0; i < ch1.size(); i++) {
        if (i && ch1[i] <= 'Z') a[++idx_a] = res, res = id(ch1[i]);
        else res = res * P + id(ch1[i]);
    }
    res = 0;
    for (int i = 0; i < ch2.size(); i++) {
        if (i && ch2[i] <= 'Z') b[++idx_b] = res, res = id(ch2[i]);
        else res = res * P + id(ch2[i]);
    }
}

int main()
{
    cin >> ch1 >> ch2;

    init();

    for (int i = 1; i <=idx_a; i++)
    {
        for (int j = 1; j <=idx_b; j++)
        {
            dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            if (a[i] == b[j]) dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
        }
    }

    cout << dp[idx_a][idx_b];
}
  • 40
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 60
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

学习算法的小菜鸡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值