【codeforces 2104D,E】欧拉筛,字符串上dp

【codeforces 2104D,E】欧拉筛,字符串上dp

Problem - D - Codeforces

题意:

给定一个长度为 n n n的数组 a 1 , a 2 , . . . , a n a_1, a_2, ... , a_n a1,a2,...,an,其中 2 ≤ a i ≤ 1 0 9 2 \leq a_i \leq 10^9 2ai109 1 ≤ n ≤ 4 × 1 0 5 1 \leq n \leq 4 \times 10^5 1n4×105

我们可以进行以下的操作任意次(或者不进行)

  • 花费一块钱,可以选择数组中任意一个元素,将其增加 1 1 1
  • 得到一块钱,并任选数组中的元素并将其减少 1 1 1

定义理想数组为:

  • 每个元素都大于等于 2 2 2
  • 数组中元素任选两个元素都互质。若数组的长度小于 2 2 2,该条件自动满足

初始的数组 a a a可能是理想的,也可能不是。数组 a a a可以通过上述操作变成理想数组,也有可能变不成。现在我们可以删除一些元素,使得无法变成的数组可以变成。问最少的删除次数是多少?(若为 0 0 0,表明初始的数组 a a a已经是理想数组或可以通过上述两种操作变成理想数组)


思路:

性质 1 1 1:首先分析两种操作,发现通过这两种操作,我们可以将数组 a a a变成相同长度的,所有元素和比当前数组小的任意数组。

证明:若将一个数字减少 x x x,那么另一个数字最多可以增加 x x x。当然,另一个数字增加的数量完全可以小于 x x x。当然,另一个数字完全可以选择减少。所以我们可以构造出任意所有元素和比之前小的数组。

注意到,若想使得所有元素两两互质,那么所有数必须是质数。

我们先筛出 4 × 1 0 5 4 \times 10^5 4×105个质数,计算前缀和,记为 p r e P r i m e s [ i ] prePrimes[i] prePrimes[i]

将数组元素从大到小排序,计算前缀和,记为 p r e N u m [ i ] preNum[i] preNum[i]

若条件 p r e N u m [ i ] ≥ p r e P r i m e s [ i ] ( 1 ≤ i ≤ n ) preNum[i] \geq prePrimes[i](1 \leq i \leq n) preNum[i]prePrimes[i](1in)满足,则一定可以通过性质 1 1 1得到理想数组。此时删除的元素数量是 n − i n - i ni

为什么数组从大到小排序,每次删除最小的元素?质数从小到大计算前缀和?因为这样更容易满足 p r e N u m [ i ] > p r e P r i m e s [ i ] preNum[i] > prePrimes[i] preNum[i]>prePrimes[i]


代码如下:

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define endl "\n"

#define int LL

const int N = 4e5 + 10, M = 6e6 + 10;
int n, a[N], s[N];
bool st[M];
vector<int> primes, sp;

void init(int n) {
	for (int i = 2; i <= n; i ++) {
		if (st[i] == false) primes.push_back(i);
		for (int j = 0; j < primes.size() && primes[j] <= n / i; j ++) {
			st[i * primes[j]] = true;
			if (i % primes[j] == 0) break;
		}
	}
	int cnt = primes.size();
	primes.insert(primes.begin(), 0);
	sp.push_back(0);
	int sum = 0;
	for (int i = 1; i <= cnt; i ++) {
		sum += primes[i];
		sp.push_back(sum);
	}
}
	
void solve() {
	cin >> n;
	for (int i = 1; i <= n; i ++) cin >> a[i];
	sort(a + 1, a + n + 1, greater<int>());
	memset(s, 0, sizeof s);
	for (int i = 1; i <= n; i ++) s[i] = s[i - 1] + a[i];
	int res = 1e9;
	for (int i = n; i >= 1; i --) {
		if (s[i] >= sp[i]) {
			res = min(res, n - i);
			break;
		}
	}
	cout << res << endl;
}

signed main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	init(6000000);
	int t; cin >> t;
	while (t --) solve();
	return 0;
}

Problem - E - Codeforces

题意:

给定一个字符串 s s s,其长度为 n n n。给出 q q q个查询,每个查询给出一个字符串 t t t,问再往字符串 t t t后加几个字符可以使得 t t t不是 s s s的子序列。(其中 s s s t t t只能由小写字母的前 k k k个字母组成)。


思路:

我们希望知道两件事情:

  1. 快速知道字符串 t t t s s s的前几个字符的子序列
  2. s s s的第 i i i个字符开始,还需要加几个字符,可以使 i i i跳到 n + 1 n + 1 n+1

初始化 n x t [ i ] [ j ] nxt[i][j] nxt[i][j]数组,表示第 i i i个位置往后看, A S C I I ASCII ASCII码为 j + 97 j + 97 j+97的字符出现的第一次出现的位置。初始化时倒着初始化很方便。 l s t [ i ] lst[i] lst[i]表示 A S C I I ASCII ASCII码为 i + 97 i + 97 i+97的字符上一次出现的位置

d p [ i ] dp[i] dp[i]表示前 i i i个字符已经完成了匹配,还需加几个字符使得字符串不是 s s s的子序列。 d p dp dp数组倒着转移。

对于给定的询问,我们先将指针 i i i跳到位置 i d x idx idx,那么 d p [ i d x ] dp[idx] dp[idx]就是答案

为什么这样定义状态?考虑指针 i i i跳的过程。遇见不同的字符串 t t t时, i i i是变量


代码如下:

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define endl "\n"

const int N = 1e6 + 10, M = 30;
int n, k, q;
char a[N];
int nxt[N][M], lst[M], dp[N];

int main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> k;
	for (int i = 1; i <= n; i ++) cin >> a[i];
	for (int i = 0; i < k; i ++) {
		lst[i] = n + 1;
		nxt[n + 1][i] = n + 1;
	}	
	
	memset(dp, 0x3f, sizeof dp);
	dp[n + 1] = 0;
	
	for (int i = n; i >= 0; i --) {
		for (int j = 0; j < k; j ++) {
			nxt[i][j] = lst[j];
			dp[i] = min(dp[i], dp[nxt[i][j]] + 1);
		}	
		if (i != 0) lst[a[i] - 'a'] = i;
	}
	
	cin >> q;
	while (q --) {
		string str;
		cin >> str;
		int idx = 0;
		for (int i = 0; i < str.size(); i ++) {
			idx = nxt[idx][str[i] - 'a'];
		}
		cout << dp[idx] << endl;
	}
	
	return 0;
}
区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值