[UOJ#214]合唱队形

51 篇文章 0 订阅
41 篇文章 0 订阅

题目大意

有n个人每个人有一个字符集初始为空。
有tot个事件,每个事件形如在第ai个人的字符集里放了bi这个小写字母。
每个时刻等概率发生一个事件。
有一个长度为m的字符串s。某个时刻如果存在编号连续的m个人,按从小到大的顺序发现对于第i个人字符集里都有si,那么则胜利。
问胜利的期望时间,要求判断无解,答案在模意义下进行。

容斥

f(i) 表示时刻i还没有胜利的概率。
答案是 i>=0f(i)
考虑容斥,枚举哪些位置作为起点在时刻t匹配成功。
f(t)=2nm+11i=0g(h(i),t)(1)bitcount(i)
h(i)表示i这个二进制状态内的起点匹配所需要发生的事件总数。
g(i,t)表示长度为t的序列,每个位置是1~tot的数,有i个数一定要出现,概率是多少。
g的计算当然可以容斥
g(i,t)=ij=0Cji(totjtot)t(1)j
发现f的求和最终变成这个地方对 (totjtot)t 的求和。
如果是真分数求和当然很好做。
注意j可以为0,那么会出现 1t 求和,怎么办?
只考虑 1t 最终的系数那么是 nm+1i=0Cinm+1(1)i
容易发现因为 n>=m 这个式子的值是0。
如果没有发现这个,我们还可以换容斥方法。
f(t)=2nm+11i=1g(h(i),t)(1)bitcount(i)+1
g(i,t)的意义改为,长度为t的序列,每个位置是1~tot的数,有i个数一定不要全部出现,概率是多少。
此时 g(i,t)=ij=1Cji(totjtot)t(1)j+1
可以发现现在就不带 1t 的项了。
新的式子可以考虑每种情况被计算的次数证明正确性。
回到原来的地方。
然后注意 t>=0xt=11x ,这个可能涉及求逆元,请进行预处理避免复杂度多带一个log。
这个做法的时间复杂度是 O(2nmtot) ,只有m较大才能用。
对于m较小的情况,不妨设dp[i,s,k]表示确定到第i个位置,(i-m,i)里成为起点的二进制状态为s,带权方案数是多少(因为注意最后有-1的系数)。那么也很好做了,复杂度是 O(2mntot) ,当然因为我好像毛都没预处理所以我的程序是 O(2mnmtot)

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define two(i) (1<<(i))
using namespace std;
typedef long long ll;
const int maxn=30+2,mo=998244353;
char s[maxn],h[maxn];
bool dis[maxn][maxn];
int b[maxn][maxn],d[maxn],cnt[maxn*maxn],niy[maxn*maxn];
int c[maxn*maxn][maxn*maxn],f[2][two(15)][maxn*26];
int i,j,k,l,r,t,n,m,now,tot,ni,ans,ca;
bool czy;
int qsm(int x,int y){
    if (!y) return 1;
    int t=qsm(x,y/2);
    t=(ll)t*t%mo;
    if (y%2) t=(ll)t*x%mo;
    return t;
}
bool pd(){
    int i,j;
    fo(i,1,n-m+1){
        fo(j,1,m+1)
            if (j>m||!dis[i+j-1][s[j]-'a']) break;
        if (j>m) return 1;
    }
    return 0;
}
int calc(int x){
    if (x==1) return 0;
    else return qsm(1-x,mo-2);
}
int g(int cnt){
    int i,l,t=0;
    fo(i,0,cnt){
        l=(ll)c[cnt][i]*/*calc((ll)(tot-i)*ni%mo)%mo*/niy[i]%mo;
        if (i%2) (t-=l)%=mo;else (t+=l)%=mo;
    }
    return t;
}
void dfs(int x,int f,int y){
    if (x==n-m+2){
        (ans+=f*g(y))%=mo;
        return;
    }
    dfs(x+1,f,y);
    int i,t=0;
    bool czy=1;
    fo(i,x,x+m-1)
        if (!dis[i][s[i-x+1]-'a']){
            czy=0;
            break;
        }
        else{
            b[i][s[i-x+1]-'a']++;
            if (b[i][s[i-x+1]-'a']==1) t++;
        }
    if (czy){
        dfs(x+1,-f,y+t);
        fo(i,x,x+m-1) b[i][s[i-x+1]-'a']--;
    }
}
void brute(){
    ans=0;
    dfs(1,1,0);
    (ans+=mo)%=mo;
    printf("%d\n",ans);
}
void solve(){
    fo(i,0,1)
        fo(j,0,two(m-1)-1)
            fo(k,0,tot) f[i][j][k]=0;
    f[now=1][0][0]=1;
    fo(i,1,n){
        fo(j,0,two(m-1)-1)
            fo(k,0,tot) f[1-now][j][k]=0;
        fo(j,0,two(m-1)-1)
            fo(k,0,tot)
                if (f[now][j][k]){
                    fo(t,0,25) d[t]=0;
                    r=0;
                    czy=1;
                    l=j;
                    fd(t,m,2){
                        if (l%2==1){
                            if (!dis[i][s[t]-'a']){
                                czy=0;
                                break;
                            }
                            d[s[t]-'a']++;
                            if (d[s[t]-'a']==1) r++;
                        }
                        l/=2;
                    }
                    if (!czy) continue;
                    (f[1-now][j/2][k+r]+=f[now][j][k])%=mo;
                    if (i+m-1<=n&&dis[i][s[1]-'a']){
                        if (!d[s[1]-'a']) r++;
                        if (m==1) (f[1-now][0][k+r]-=f[now][j][k])%=mo;
                        else (f[1-now][j/2+two(m-2)][k+r]-=f[now][j][k])%=mo;
                    }
                }
        now=1-now;
    }
    fo(k,0,tot) cnt[k]=0;
    fo(j,0,two(m-1)-1)
        fo(k,0,tot) (cnt[k]+=f[now][j][k])%=mo;
    ans=0;
    fo(k,0,tot) (ans+=(ll)cnt[k]*g(k)%mo)%=mo;
    (ans+=mo)%=mo;
    printf("%d\n",ans);
}
int main(){
    //freopen("ex_B4.in","r",stdin);
    scanf("%d",&ca);
    while (ca--){
        scanf("%d%d",&n,&m);
        fo(i,1,n)
            fo(j,0,25) dis[i][j]=0;
        tot=0;
        fo(i,1,n){
            scanf("%s",h+1);
            t=strlen(h+1);
            tot+=t;
            fo(j,1,t) dis[i][h[j]-'a']=1;
        }
        scanf("%s",s+1);
        if (!pd()){
            printf("-1\n");
            continue;
        }
        ni=qsm(tot,mo-2);
        fo(i,1,tot) niy[i]=qsm(1-(ll)(tot-i)*ni%mo,mo-2);
        c[0][0]=1;
        fo(i,1,tot){
            c[i][0]=1;
            fo(j,1,i) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mo;
        }
        if (n-m+1<=16) brute();
        else solve();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值