2019HDU多校第一场 String 贪心

题意:给你一个字符串,问是否存在一个长度为m的子序列,子序列中对应字符的数目必须在一个范围内,问是否存在这样的字符串?如果存在,输出字典序最小的那个。

思路:贪心,先构造一个序列自动机,序列自动机指向在它后面离它最近的某个字符的位置。对于当前位置,从a开始枚举字符,如果答案串的下个位置填这个字符可以,就立马填上这个字符,最后看一下贪心构造的字符串长度是不是m就可以了。

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int cnt[26], last[26], l[26], r[26];
int ssum[maxn][26], Next[maxn][26];
int n, m, tot;
char s[maxn], ans[maxn];
bool valid(int x) {
	if(x == n + 1) {
		for (int i = 0; i < 26; i++) {
			if(cnt[i] >= l[i] && cnt[i] <= r[i]) continue;
			return 0;
		}
		if(tot != m) return 0;
		return 1;
	}
	bool flag = 1;
	cnt[s[x] - 'a']++;
	tot++;
	for (int i = 0; i < 26; i++) {
		if(ssum[x + 1][i] + cnt[i] >= l[i] && cnt[i] <= r[i]) continue;
		else flag = 0;
	}
	int tmp = 0;
	for (int i = 0; i < 26; i++) {
		tmp += max(l[i] - cnt[i], 0);
	}
	if(m - tot > n - x + 1) flag = 0;
	if(m - tot < tmp) flag = 0;
	if(flag == 0) {
		tot--;
		cnt[s[x] - 'a']--;
		return 0;
	}
	return 1;
}
int main() {
	while(~scanf("%s", s + 1)) {
		tot = 0;
		scanf("%d", &m);
		for (int i = 0; i < 26; i++) {
			scanf("%d%d", &l[i], &r[i]);
		}
		n = strlen(s + 1);
		memset(ssum[n + 1], 0, sizeof(ssum[n + 1]));
		memset(cnt, 0, sizeof(cnt));
		for (int j = 0; j < 26; j++) last[j] = n + 1;
		for (int i = n; i >= 1; i--) {
			for (int j = 0; j < 26; j++) {
				ssum[i][j] = ssum[i + 1][j];
				Next[i][j] = last[j];
			}
			ssum[i][s[i] - 'a']++;
			last[s[i] - 'a'] = i;
		}
		for (int i = 0; i < 26; i++)
			Next[0][i] = last[i];
		int pos = 0;
		while(pos <= n && tot < m) {
			bool flag = 0;
			for (int j = 0; j < 26; j++) {
				if(valid(Next[pos][j])) {
					ans[tot] = 'a' + j;
					pos = Next[pos][j];
					flag = 1;
					break;
				}
 			}
 			if(flag == 0) break;
		}
		if(tot == m) {
			ans[tot + 1] = 0;
			printf("%s
", ans + 1);
		} else {
			printf("-1
");
		}
	}
}

  

原文地址:https://www.cnblogs.com/pkgunboat/p/11228805.html