[Codeforces 1009G] Allowed Letters

题目链接: 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(n626) 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值