2022.10.22每日刷题打卡(补

Expenditure Reduction

复盘一下周赛的一道题,磕了很久花了大概3h去做呜呜呜,还是有必要复盘一下的。

题意很简单给两个字符串s和t,删掉s的一段前缀和s的一段后缀,仍然保证t是s的子序列的条件下的最短字符串。

最开始第一眼看到就想着去用KMP或双指针去做,但推了推发现都不可行,最后认真想过发现用dp去维护字符串的前缀不用考虑中间的状态,只用关心结尾是不是以字符串t最后一个字符为结尾就好了。

首先定义字符串t结尾的字符为tt,那么对于字符串s他可能会出现很多个tt,这样就会妨碍我们去更新dp的状态,所以我们可以把下标和字符绑在一起存起来,这样我们维护去维护dp状态的时候就会很轻松,那么问题是怎们去把下标和字符绑定起来呢,首先字符串中的字符有'0'-'9'和'a'-'z'总共有36个字符我们把他们转化为数字,ASCII码转化的话遍历的时候并不太好遍历,所以我们可以写个函数把这些字符转化为0-35的数字此函数如下:

	std::function<int(char)> vule = [&](char c) {
		if (c <= '9' && c >= '0')return c - '0';
		else return c - 'a' + 10;
	};

那么接下来就是下标与s中的字符一一绑定,可以用stl容器去处理,这里我选择的是用二维数组去存储,另外可以考虑到的是我们要找的是包含t的最短子串,那么我们肯定要处理最近的那个,最短子串最开始的地方也要不断地去更新开始的位置即可。那么预处理所有前缀状态的dp如下:

	for (int i = 1; i <= lens; i++) {
		for (int j = 0; j < 36; j++) {
			dp1[i][j] = dp1[i - 1][j];
			dp1[i][vule(s[i])] = i;
		}
	}

预处理字符串s的dp做完之后,就进入最终答案dp了,定义字符串t的首个字符是t0最后的dp要注意更新s字符串中所有t0字符状态,因为后面肯定要找头再找尾。

	char head = t[1];
	for (int i = 1; i <= lens; i++) {
		if (s[i] == head) {
			dp2[1][i] = i;
		}
	}

最后就是dp完整的状态转移了 ,再说一下这个dp2[i][j]状态表达,i代表字符串t[1-i]的前缀状态,j代表以s[j]字符为结尾的字符串状态;

	for (int i = 2; i <= lent; i++) {
		for (int j = 1; j <= lens; j++) {
			int ch = vule(t[i - 1]);
			if (s[j] == t[i] && dp1[j - 1][ch]) {
				dp2[i][j] = dp2[i - 1][dp1[j - 1][ch]];
			}
		}
	}

 最后就是遍历状态中最短的字符串就可以了;

	int r = 0, ans = 1e9;
	for (int i = 1; i <= lens; i++) {
		if (s[i] == t[lent] && dp2[lent][i] != -1) {
			if (i - dp2[lent][i] + 1 < ans) {
				ans = i - dp2[lent][i] + 1;
				r = i;
			}
		}
	}

 另外千万别memset!千万别memset!!!千万别memset!!!会超时!!!会超时!!!会超时!!!会超时!!!

最后完整代码:

#include <bits/stdc++.h>
typedef long long ll;
const int N = 1e5 + 9;
int dp1[N][40], dp2[150][N];
void solve() {
	std::string s, t;
	std::cin >> s >> t;
	int lens = s.size(), lent = t.size();
	s = "." + s;
	t = "." + t;
//	memset(dp1, -1, sizeof dp1);
//	memset(dp2, -1, sizeof dp2);
	for (int i = 1; i <= lent; i++) {
		for (int j = 1; j <= lens; j++) {
			dp2[i][j] = -1;
		}
	}
	std::function<int(char)> vule = [&](char c) {
		if (c <= '9' && c >= '0')return c - '0';
		else return c - 'a' + 10;
	};
	for (int i = 1; i <= lens; i++) {
		for (int j = 0; j < 36; j++) {
			dp1[i][j] = dp1[i - 1][j];
			dp1[i][vule(s[i])] = i;
		}
	}
	char head = t[1];
	for (int i = 1; i <= lens; i++) {
		if (s[i] == head) {
			dp2[1][i] = i;
		}
	}
	for (int i = 2; i <= lent; i++) {
		int k = vule(t[i - 1]);
		for (int j = 1; j <= lens; j++) {
			if (s[j] == t[i] && dp1[j - 1][k]) {
				dp2[i][j] = dp2[i - 1][dp1[j - 1][k]];
			}
		}
	}
	int r = -1, ans = 1e9;
	for (int i = 1; i <= lens; i++) {
		if (s[i] == t[lent] && dp2[lent][i] != -1) {
			if (i - dp2[lent][i] + 1 < ans) {
				ans = i - dp2[lent][i] + 1;
				r = i;
			}
		}
	}
	std::cout << s.substr(r - ans + 1, ans) << "\n";
}

signed main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int t;
	std::cin >> t;

	while (t--) {
		solve();
	}

	return 0;
}

 

比赛中 代码跑了886ms很极限了hh

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值