【枚举】hihocoder1646 Rikka with String II

11 篇文章 0 订阅
9 篇文章 0 订阅
这道题目是关于帮助萌萌哒六花解决数学问题,涉及01字符串的权值计算。权值是将字符串插入空Trie树后得到的节点数。给定包含01?的字符串,需要计算所有可能字符串集合的权值之和,利用容斥原理可以解决此问题。由于字符串数量限制,可以暴力枚举子集并计算每个子集的前缀总数,最后得出答案。这是一道用到基础数据结构和算法的题目,提醒程序员注意身体健康。
摘要由CSDN通过智能技术生成

描述

众所周知,萌萌哒六花不擅长数学,所以勇太给了她一些数学问题做练习,其中有一道是这样的:

对于 n 个 01 字符串 si,定义他们的权值是这 n个 si 的串插入一个空的 Trie 树后得到的结果 Trie 中的节点个数。例如 [“01”,”00”] 的权值是4,[“010”,”1”] 的权值是5。

现在勇太给出了 n 个只包含 01? 的字符串 si。其中 ? 表示既有可能是 0 也有可能是 1。显然,如果有 K 个 ?,那么一共有 2K 个可能的字符串集合。

现在勇太想要知道对于这 2K 种状态,权值的和是多少。

当然,这个问题对于萌萌哒六花来说实在是太难了,你可以帮帮她吗?


分析

对于每一种方案而言,其节点总数其实就是所有不重复的公共前缀数。
这里需要用的容斥原理:

这里写图片描述
具体的证明在baidu上还是比较清晰的

因为最多只有20个数列,所以我们可以暴力枚举其中的一个子集,再 O|S| 的复杂度来求出当前子集在所有方案中的前缀总数,设这个值为 f(S)

那么答案就是

S(1)(|S|+1)f(S)
,说白了就是根据子集的大小的奇偶性来决定是加或减。
具体实现可以参见代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define SF scanf
#define PF printf
#define MOD 998244353
#define MAXN 25
#define MAXL 55
using namespace std;
int n,tot,sum;
char s[MAXN][MAXL];
long long slen,ans1,pw[MAXN*MAXL];
vector<int> a;
int main(){
    SF("%d",&n);
    for(int i=0;i<n;i++){
        SF("%s",s[i]);
        int len=strlen(s[i]);
        slen+=len;
    }
    for(int i=0;i<n;i++){
        int len=strlen(s[i]);
        for(int j=0;j<len;j++)
            if(s[i][j]=='?')
                tot++;
    }
    pw[0]=1;
    for(int i=1;i<=tot;i++)
        pw[i]=(pw[i-1]<<1ll)%MOD;
    slen=(pw[tot]*slen)%MOD;
    for(int s1=1;s1<(1<<n);s1++){
        int len=MAXL,pre=0;
        sum=tot;
        long long ans=0;
        a.clear();
        for(int i=0;i<n;i++)
            if(s1&(1<<i))
                a.push_back(i);
        for(int i=0;i<a.size();i++){
            int len1=strlen(s[a[i]]);
            len=min(len1,len);
        }
        int j;
        for(j=0;j<len;j++){
            int flag=-1;
            int nowl=0;
            for(int i=0;i<a.size();i++)
                if(s[a[i]][j]=='?'){
                    nowl++;
                }
                else if(s[a[i]][j]=='0'){
                    if(flag==1){
                        flag=2;
                        break;
                    }
                    else
                        flag=0;
                }
                else if(s[a[i]][j]=='1'){
                    if(flag==0){
                        flag=2;
                        break;
                    }
                    else
                        flag=1;
                }
            sum-=nowl;
            if(flag==2){
                break;
            }
            else if(flag==-1){
                pre++;
            }
            ans=(ans+pw[pre+sum])%MOD;
        }
        if(a.size()%2==1)
            ans1=(ans1+ans)%MOD;
        else
            ans1=(ans1+MOD-ans)%MOD;
    }
    PF("%lld",(ans1+pw[tot])%MOD);

}

说来也惭愧。。可能是当时因为感冒发烧,脑子不好使,这么简单一道水题居然看了半天。。题解都看了好久才看懂。。。OIers一定要注意身体啊。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值