Codeforces Round 47 (Rated for Div. 2) G. Allowed Letters Hall定理

题目链接: http://codeforces.com/contest/1009/problem/G

题意:

给你字母 ′ a ′ − ′ f ′ 'a'-'f' af 的个数,现在给你 m m m 个条件,每个条件要求,位置 p o s pos pos 上可以放的合法字符集 S S S ,现在要你求合法的最小字典序的字符串(如果不存在输出“不可能”)。

做法:

枚举从小到大的字母肯定是跑不了了,但是枚举之后的验证就很重要了。

一直错的原因就是因为没弄明白内核,先说为什么错,可能蛮多人和我一样原来弄不懂的。开始的时候我把所谓的 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示成了,从位置 i − n i-n in 中有多少个状态 j j j ,这样的话在后面就能做处理了,当要求的数字个数不满足要求的时候,那么就不能放当前的字母。

但是这样想其实是不对的,比如说。举个栗子:
a b f f f c abfffc abfffc
6 6 6
1 1 1 d f df df
2 2 2 a f af af
3 3 3 a c ac ac
4 4 4 a b ab ab
5 5 5 b b b
6 6 6 b d f bdf bdf
答案是 f f c a b f ffcabf ffcabf

在第二个字母放下的时候,因为后面当中的状态 a b ab ab 只需要一个 b b b 合法,状态 b b b 也是需要一个 b b b 合法,但是如果我把 a a a 放在这里,这两个就不能同时满足条件了。即后面每个状态满足,也不能保证值一定是够用的。

然后就要引入这个Hall定理。

二部图G中的两部分顶点组成的集合分别为X, Y. X = X 1 , X 2 , X 3 , X 4 , . . . . . . . . . , X m , Y = Y 1 , Y 2 , Y 3 , Y 4 , . . . . . . . . . , Y n X={X_1, X_2, X_3,X_4,.........,X_m}, Y={Y_1, Y_2, Y_3, Y_4 ,.........,Y_n} X=X1,X2,X3,X4,.........,Xm,Y=Y1,Y2,Y3,Y4,.........,Yn ,G中有一组无公共点的边,一端恰好为组成X的点的充分必要条件是:
X中的任意k个点至少与Y中的k个点相邻。 ( 1 ≤ k ≤ m ) (1≤k≤m) 1km)

在这里的体现就是,对于任意一种状态 S S S 里的字母 ,都要有超过其要求字母的数量存在,这样才能使得在最后所有的状态都能满足合法的条件。 即在上面的 X X X 都被满足。

代码

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=100005;
const int N=(1<<6);
char s[maxn],op[10],ans[maxn];
int n,m,las[maxn][N+1];
int nu[10],val[maxn];
int main(){
    scanf("%s%d",s+1,&m);
    n=strlen(s+1);
    for(int i=1;i<=n;i++){
        nu[s[i]-'a']++;
    }
    rep(i,1,m){
        int p; scanf("%d%s",&p,op);
        for(int j=0;j<strlen(op);j++){
            int a=op[j]-'a';
            val[p]|=(1<<a);
        }
    }
    for(int i=n;i>=1;i--){
        if(val[i]==0) val[i]=N-1;
        for(int j=0;j<N;j++){
            las[i][j]=las[i+1][j];
            if(j&val[i]) las[i][j]++;
        }

    }
    int flag=0;
    for(int i=1;i<=n;i++){
        int ok=0;
        for(int j=0;j<6;j++){
            if((val[i]&(1<<j))==0||nu[j]==0) continue;
            nu[j]--;
            int can=1;
            for(int k=1;k<N;k++){
                int tmp=0;
                for(int p=0;p<6;p++){
                    if((k&(1<<p))==0) continue;
                    tmp+=nu[p];
                }
                if(tmp>las[i+1][k]) can=0;
            }
            if(can){
                ans[i]='a'+j;
                ok=1;
                break;
            }
            nu[j]++;
        }
        if(!ok){
            flag=1; break;
        }
    }
    if(flag) printf("Impossible\n");
    else {
        rep(i,1,n){
            printf("%c",ans[i]);
        }
        printf("\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值