【洛谷4045】[JSOI2009] 密码(状压+AC自动机上DP)

点此看题面

大致题意: 给你\(n\)个字符串,问你有多少个长度为\(L\)的字符串,使得这些字符串都是它的子串。若个数不大于\(42\),按字典序输出所有方案。

状压

显然,由于\(n\)很小,我们可以把每个字符串是否出现过状压起来。

这样就可以起到极大的优化作用。

我们可以对每个节点开一个变量\(Ex\)记录一下该节点存在哪些字符串,转移起来就很方便了。

\(AC\)自动机上\(DP\)

由于和子串有关,这题可以看做是一个多模匹配问题。

所以,我们考虑建出\(AC\)自动机。

然后,我们就可以\(DP\)了。

\(f_{p,i,j}\)表示现在是第\(p\)个字符,走到\(AC\)自动机上第\(i\)个节点,每个字符串出现状态状压起来为\(j\)的方案数。

转移应该是比较套路的,每次把信息向子节点转移,同时\(j\)要或上子节点的\(Ex\)值。

最后的答案就是\(\sum f_{L,i,2^n-1}\)

具体方案

我们对每个状态开个\(vector\)记录一下能从哪些非\(0\)状态转移过来,然后倒着\(dfs\)一遍就可以求出所有方案了。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 10
#define M 25
#define LL long long
using namespace std;
int n,m;
class AcAutomation//AC自动机
{
    private:
        #define pb push_back
        int rt,Nt;short q[N*M+5];long long f[M+5][N*N+5][1<<N];
        short cnt;string res[45];struct Trie {int Ex,F,S[30];}O[N*N+5];
        struct Status
        {
            short y,z,v;I Status(CI b=0,CI c=0,CI k=0):y(b),z(c),v(k){}
        };vector<Status> g[M+5][N*N+5][1<<N];
    public:
        I AcAutomation() {rt=Nt=1;}
        I void Insert(CI p,Con string& s)//插入字符串
        {
            RI i,l=s.length(),x=rt,k;for(i=0;i^l;++i)
                !O[x].S[k=s[i]&31]&&(O[x].S[k]=++Nt),x=O[x].S[k];
            O[x].Ex|=1<<p-1;
        }
        I void Build()//建AC自动机
        {
            RI i,j,k,p,H=1,T=0;for(i=1;i<=26;++i)
            {
                if(!(p=O[rt].S[i])) {O[rt].S[i]=rt;continue;}
                O[q[++T]=p].F=rt,O[p].Ex|=O[O[p].F].Ex;
            }
            W(H<=T) for(k=q[H++],i=1;i<=26;++i)
            {
                if(!(p=O[k].S[i])) {O[k].S[i]=O[O[k].F].S[i];continue;}
                O[q[++T]=p].F=O[O[k].F].S[i],O[p].Ex|=O[O[p].F].Ex;
            }
        }
        I void Travel(CI x,CI y,CI z,Con string& s)//倒序找出所有方案
        {
            if(!x) return (void)(res[++cnt]=s);vector<Status>::iterator it;
            for(it=g[x][y][z].begin();it!=g[x][y][z].end();++it) Travel(x-1,it->y,it->z,(char)(96+it->v)+s);
        }
        I void Solve()//DP求解
        {
            RI p,i,j,k,t=1<<n;long long ans=0;for(f[0][1][0]=1,p=1;p<=m;++p) for(i=1;i<=Nt;++i)//DP转移求解第一个询问
                for(j=0;j^t;++j) for(k=1;k<=26;++k) f[p][O[i].S[k]][j|O[O[i].S[k]].Ex]+=f[p-1][i][j];
            for(i=1;i<=Nt;++i) ans+=f[m][i][t-1];if(printf("%lld\n",ans),ans>42) return;//统计答案并输出
            for(p=1;p<=m;++p) for(i=1;i<=Nt;++i) for(j=0;j^t;++j)//DP转移求解第二个询问
                for(k=1;k<=26;++k) f[p-1][i][j]&&(g[p][O[i].S[k]][j|O[O[i].S[k]].Ex].pb(Status(i,j,k)),0);
            for(i=1;i<=Nt;++i) f[m][i][t-1]&&(Travel(m,i,t-1,""),0);//找方案
            for(sort(res+1,res+ans+1),i=1;i<=ans;++i) cout<<res[i]<<endl;//输出方案
        }
}A;
int main()
{
    RI i;string s;for(scanf("%d%d",&m,&n),i=1;i<=n;++i) cin>>s,A.Insert(i,s);
    return A.Build(),A.Solve(),0;
}

转载于:https://www.cnblogs.com/chenxiaoran666/p/Luogu4045.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值