Task On The Board(构造、贪心)
题意
给定一个由小写字母构成的字符串\(s\),从中选取\(m\)个字符构成一个新的字符串\(t\)(字符可乱序),满足一个序列\(b\)
序列\(b\)的定义如下:
\(b_i = \sum_{t_j > t_i}|j - i|, 1 \leq i, j \leq |t|\)
数据范围
\(1 \leq T \leq 100\)
\(1 \leq |s| \leq 50\)
\(1 \leq m \leq |s|\)
\(0 \leq b_i \leq 1225\)
思路
考察\(b_i = 0\)的位置。满足\(b_i = 0\)的字符意味着在\(t\)中,没有比其更大的字符。
因此,我们可以从大到小遍历所有字符,若当前字符的个数不少于\(b_i = 0\)的个数,那么这些位置就可以用这个字符填充。
因为填充的这些字符一定比其他位置上的字符大,因此可以更新其他位置的\(b_j\),即\(b_j = b_j - |i - j|\)
然后再找\(b_j = 0\)的位置,并从从大到小遍历比当前已填充字符小的字符,后面操作同上,直到填充完毕。
数据保证一定有解,那么这种构造方法一定是对的。
反证法证明,若不找最大字符填充,而是找另外的字符填充。我们考察下一步可选字符的集合,前者设为\(A\),后者设为\(B\)。
由于\(B\)是\(A\)的子集,因此,若用最大字符填充的方法不对,那么其他填充方法也不对,故原贪心策略正确。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int N = 55;
int n;
char s[N], ans[N];
int b[N];
int is_cur[N];
int main()
{
int T;
scanf("%d", &T);
while(T --) {
scanf("%s", s + 1);
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d", &b[i]);
memset(ans, '\0', sizeof(ans));
memset(is_cur, 0, sizeof(is_cur));
unordered_map<char, int> mp;
int len = strlen(s + 1);
for(int i = 1; i <= len; i ++) {
mp[s[i]] ++;
}
int cnt = 0;
int loc = 25;
while(cnt < n) {
int num = 0;
for(int i = 1; i <= n; i ++) {
if(!b[i]) {
num ++;
is_cur[i] = 1;
}
}
char ch;
for(int i = loc; i >= 0; i --) {
char tmp = i + 'a';
if(mp[tmp] >= num) {
ch = tmp;
loc = i - 1;
break;
}
}
for(int i = 1; i <= n; i ++) {
if(!b[i] && is_cur[i] == 1) {
b[i] = -1;
is_cur[i] = -1;
ans[i] = ch;
cnt ++;
for(int j = 1; j <= n; j ++) {
if(b[j]) {
b[j] -= abs(i - j);
}
}
}
}
}
printf("%s\n", ans + 1);
}
return 0;
}