【loli的胡策】训练1.7(hash+记忆化搜索+AC自动机+矩阵优化dp)

本文解析了BZOJ2011竞赛中的三道经典题目:Matrix、符环及禁忌。Matrix题通过Hash算法进行快速匹配;符环题利用DP算法解决括号匹配问题;禁忌题则结合AC自动机与矩阵快速幂实现最优字符串构造。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

T1

[BZOJ2462][BeiJing2011]Matrix

题解:

今天对于这道题有一个很清晰的思路,想着把一维hash写对,可是一维一拍一个错?!后来把一维的交上然后A了,再拍也是对的,这都什么啊这都是。
其实很简单啦,就是把每一行建立一个hash,然后暴力寻找就行啦

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ull unsigned long long
using namespace std;
const int base=23,mod=998244353;
int n,m,a,b;
ull hash[1005][1005],mi[1005],ask[1005][1005];
char s[1005];
int main()
{
    scanf("%d%d%d%d",&m,&n,&a,&b);
    mi[0]=1;for (int i=1;i<=n;i++) mi[i]=mi[i-1]*base%mod;
    for (int i=1;i<=m;i++)
    {
        scanf("%s",s+1);
        ull ans=0;
        for (int j=1;j<=n;j++)
        {
            ans=(ans*base+s[j])%mod;
            hash[i][j]=ans;
        }
    } 
    int q;scanf("%d",&q);
    while (q--)
    {
        for (int i=1;i<=a;i++)
        {
            scanf("%s",s+1);
            ull ans=0;
            for (int j=1;j<=b;j++)
            {
                ans=(ans*base+s[j])%mod;
                ask[i][j]=ans;
            }
        } 
        bool fff=0;
        for (int i=1;i<=m-a+1 && !fff;i++)
          for (int j=1;j<=n-b+1 && !fff;j++)
            if ((hash[i][j+b-1]-(hash[i][j-1]*mi[b]%mod)+mod)%mod==ask[1][b]) 
            {
                fff=1;
                for (int z=1;z<a;z++)
                  if ((hash[i+z][j+b-1]-(hash[i+z][j-1]*mi[b]%mod)+mod)%mod!=ask[z+1][b]) 
                   {fff=0;break;}
            }
        if (fff) printf("1\n");else printf("0\n");
    }
}

T2

[BZOJ2461][BeiJing2011]符环

题解:

loli带我们去北京?!
20pts的全’S’不难发现就是Catalan数列
30pts的爆搜就好,但我竟然写挂了
统计方案数当然要考虑DP。因为填好了前n个字符以后,根据它给定的信息,后n个就被唯一确定了,所以只要DP前n个即可。
然而还要求整个串是一个完整的括号匹配,所以左右括号的匹配情况也会影响状态。
于是用f[i][x][y][z]表示当前填写到了第i个字符,[1..i]这段区间里有x个未匹配的左括号,[n+1..n+i]这段区间里有y个未匹配的右括号和z个未匹配的左括号。
求的时候分情况讨论运用记忆化搜索就行啦
注意一在右边填上’)’,就要和右边[n+1..n+i]这段区间内的未匹配的左括号匹配,不要留着不匹配,因为早晚这些多余的左括号都要和右括号匹配的

代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#define LL long long
using namespace std;
char s[55];LL f[55][55][55][55];int n;
LL dfs(int t,int x,int y,int z)//x (     y  (    z )
{
    if (t>n) return y==0&&x==z;
    if (f[t][x][y][z]!=-1) return f[t][x][y][z];
    f[t][x][y][z]=0;
    if (s[t]=='S')
    {
        if (x) 
        {
            if (y) f[t][x][y][z]+=dfs(t+1,x-1,y-1,z);
            else f[t][x][y][z]+=dfs(t+1,x-1,y,z+1);
        }
        f[t][x][y][z]+=dfs(t+1,x+1,y+1,z);
    }
    else
    {
        if (x) f[t][x][y][z]+=dfs(t+1,x-1,y+1,z);
        if (y) f[t][x][y][z]+=dfs(t+1,x+1,y-1,z);
        else f[t][x][y][z]+=dfs(t+1,x+1,y,z+1);
    }
    return f[t][x][y][z];
}
int main()
{
    freopen("mobius.in","r",stdin);
    freopen("mobius.out","w",stdout);
    int T;
    scanf("%d",&T);
    while (T--)
    {
        scanf("%s",s+1);n=strlen(s+1);
        memset(f,-1,sizeof(f));
        printf("%lld\n",dfs(1,0,0,0)); 
    }
}

T3

[BZOJ2553][BeiJing2011]禁忌

题解:

这是一道经典的AC自动机+矩阵优化dp的题目
首先我们提取题目中的关键字,可以转化成构造长为len的字符串,让ta在AC自动机上匹配到最大的禁忌串个数
首先把模式串建成AC自动机(或trie图),然后考虑在AC自动机上的转移。
对于每一步转移,都有两种情况:

1、子结点没被标记(即不是模式串的结尾单词),有1/alphabet的期望转移到这个子结点。

2、子结点被标记,有1/alphabet的期望值转移到根,并且用一个新结点记录答案。

这样我们可以考虑构造一个矩阵a[i][j],记录下来结点之间的转移关系。

然后矩阵自乘m次得到的矩阵ans,ans[0][cnt+1]就是答案。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#define LD long double

using namespace std;

int top=0,ch[100][30],n,L,A,fail[100];
char s[12][20];
bool ed[100];

struct node{
    LD H[100][100];

    node operator *(const node &a)const
    {
        node ans;
        for (int i=0;i<=top;i++)
            for (int j=0;j<=top;j++)
            {
                ans.H[i][j]=0.0;
                for (int k=0;k<=top;k++)
                    ans.H[i][j]=ans.H[i][j]+H[i][k]*a.H[k][j];
            }
        return ans;
    }

    LD KSM(int p)
    {
        node ans=(*this);
        node a=(*this);
        p--;
        while (p)
        {
            if (p&1) ans=ans*a;
            a=a*a;
            p>>=1;
        }
        return ans.H[0][top];
    }
};
node H;

void build(int bh)
{
    int now=0;
    for (int i=0;i<strlen(s[bh]);i++)
    {
        int x=s[bh][i]-'a';
        if (!ch[now][x]) ch[now][x]=++top;
        now=ch[now][x];
    }
    ed[now]=1;
}

void make_fail()
{
    queue<int> Q;
    for (int i=0;i<A;i++) if (ch[0][i])
        Q.push(ch[0][i]);
    while (!Q.empty())
    {
        int now=Q.front(); Q.pop();
        for (int i=0;i<A;i++)
        {
            if (!ch[now][i])
            {
                ch[now][i]=ch[fail[now]][i];
                continue;
            }
            fail[ch[now][i]]=ch[fail[now]][i];
            ed[ch[now][i]]|=ed[fail[ch[now][i]]];   //结尾结点的失配也是不能到达的 
            Q.push(ch[now][i]);
        }
    }
}

void make_H()
{
    LD e=(double)1/A;        //概率 
    for (int i=0;i<top;i++)
    {
        for (int j=0;j<A;j++)
            if (ed[ch[i][j]])    //下一步转移到单词结尾 
            {
                H.H[i][0]+=e;    //不能继续转移,所以只能走到0结点
                H.H[i][top]+=e;  //增加伤害期望 
            }
            else H.H[i][ch[i][j]]+=e;   //转移概率 
    }
    H.H[top][top]=1;
}

int main()
{
    scanf("%d%d%d",&n,&L,&A);
    for (int i=1;i<=n;i++)
    {
        scanf("%s",s[i]);
        build(i);
    }
    make_fail();
    make_H();
    printf("%.10lf\n",(double)H.KSM(L));
    return 0;
}

小结

今天的胡策是中游水平,怎么说呢,暴力写挂了,T1的模数选错了(这人品也是没谁了),GG

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值