G. Allowed Letters
题意:给一个长度为 n n n的字符串,字符串元素范围为 a a a到 f f f,有 m m m个限制条件,每个条件限制了某位置只能填指定元素,你要给字符串重新排列,求排列后字典序最小的字符串。
解法:不妨把重排的字符串看成二分图的左边集合,原字符串看成二分图的右边集合,我们来给它们进行字典序最小的二分图最大匹配,我们可以从前往后枚举每个位置填的最小字符,然后用 h a l l hall hall定理去 c h e c k check check,设 c n t [ s ] cnt[s] cnt[s]为原字符串集合为 s s s时包含了多少个元素,设 v a l [ i ] val[i] val[i]为第 i i i个位置能填的字符集合,设 d [ i ] [ s ] d[i][s] d[i][s]为 i − n i-n i−n的位置 s s s或 s s s的子集包含了多少个 v a l [ j ] val[j] val[j],每次我们枚举 i i i位置上填的字符 j j j,当所有的集合 s s s都满足 c n t [ s ] − ( s > > j & 1 ) = d [ i + 1 ] [ s ] cnt[s]-(s>>j\&1)=d[i+1][s] cnt[s]−(s>>j&1)=d[i+1][s]时才能去填 j j j,那么这题就写完了
#include
using namespace std;
const int maxn = 1e5 + 10;
char s[maxn];
int cnt[1 << 6], d[maxn][1 << 6], val[maxn];
int main()
{
int n, m, x;
scanf("%s", s + 1);
n = strlen(s + 1);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j < 1 << 6; j++)
if (j >> (s[i] - 'a') & 1)
cnt[j]++;
}
scanf("%d", &m);
for (int i = 1; i <= m; i++)
{
scanf("%d%s", &x, s + 1);
for (int j = 1; s[j]; j++)
val[x] |= 1 << s[j] - 'a';
}
for (int i = n; i; i--)
{
if (!val[i])
val[i] = (1 << 6) - 1;
for (int j = 1; j < 1 << 6; j++)
{
d[i][j] = d[i + 1][j];
if ((j & val[i]) == val[i])
d[i][j]++;
}
}
for (int i = 1; i <= n; i++)
{
int ok = 0;
for (int j = 0; j < 6; j++)
if (val[i] >> j & 1)
{
int okk = 1;
for (int k = 1; (k < 1 << 6) && okk; k++)
if (cnt[k] - ((k >> j) & 1) < d[i + 1][k])
okk = 0;
if (okk)
{
s[i] = j + 'a';
ok = 1;
for (int k = 1; k < 1 << 6; k++)
if (k >> j & 1)
cnt[k]--;
break;
}
}
if (!ok)
return puts("Impossible"), 0;
}
s[n + 1] = '\0';
puts(s + 1);
}