题目链接: http://codeforces.com/problemset/problem/1009/G
题目大意: 给出一个字符串s(小写’a’-‘f’构成),你可将其重新排列, 对于某一个位置i可能有允许放的字符集合限制, 求一个重排后字典序最小的满足要求的串, 无解输出”Impossible”。 (|s|≤105) ( | s | ≤ 10 5 )
题目思路: 首先是贪心, 我们从前往后依次从小到大考虑放哪个字符, 重点是判断放了这个字符后, 对于剩下的后缀是否存在合法解。 考虑每个位置的允许放的字符集合只有 26 2 6 种, 我们预处理一个后缀和f[i][j], 表示i~n中被集合j包含的个数。 考虑第i个位置放了字符c后, 要使得f[i+1][j]都 ≤ ≤ 对应的剩下的个数才能是合法的。 复杂度 O(n∗6∗26) O ( n ∗ 6 ∗ 2 6 ) 。
这里涉及到一个霍尔定理:二部图G中的两部分顶点组成的集合分别为X, Y; G中有一组无公共点的边,一端恰好为组成X的点的充分必要条件是:X中的任意k个点至少与Y中的k个点相邻。
Code:
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
const int N = (int)1e5 + 10;
int n, m; char str[N], ans[N];
int cnt[1 << 6], edge[N], f[N][1 << 6]; char tmp[10];
int main(){
scanf("%s", str + 1); n = strlen(str + 1);
for (int i = 1; i <= n; i ++)
for (int j = 0; j < (1 << 6); j ++)
if (j & (1 << (str[i] - 'a')))
cnt[j] ++;
for (int i = 1; i <= n; i ++) edge[i] = (1 << 6) - 1;
scanf("%d", &m);
for (int i = 1, x; i <= m; i ++){
scanf("%d %s", &x, tmp);
int k = strlen(tmp);
edge[x] = 0;
for (int j = 0; j < k; j ++)
edge[x] += 1 << (tmp[j] - 'a');
}
for (int i = n; i >= 1; i --)
for (int j = 0; j < (1 << 6); j ++){
f[i][j] = f[i + 1][j];
if ((j & edge[i]) == edge[i]) f[i][j] ++;
}
for (int i = 1; i <= n; i ++){
bool find = false;
for (int j = 0; j < 6 && !find; j ++)
if (cnt[1 << j] && (edge[i] & (1 << j))){
bool ok = true;
for (int k = 0; k < (1 << 6) && ok; k ++)
if (f[i + 1][k] > cnt[k] - ((k >> j) & 1))
ok = false;
if (ok){
find = true;
ans[i] = 'a' + j;
for (int k = 0; k < (1 << 6); k ++)
if (k & (1 << j))cnt[k] --;
}
}
if (!find){
puts("Impossible\n");
return 0;
}
}
puts(ans + 1);
return 0;
}