题目链接:点击查看
题目大意:给出一个长度为 n 的模式串 s ,再给出一个长度为 m 的匹配串,匹配串的每一位的可行数字都会给出,现在问最多可以匹配多少个字符串
题目分析:模式串和匹配串的匹配,AC自动机的经典问题,但是在这个题目中匹配串的个数过于多,所以并不能这样做,因为匹配串的长度比较短,所以可以预处理出一个可行数组用来记录哪个位置可以放置哪个数字,这样就能将时间复杂度优化为 n * m 暴力贪心匹配了,但是看数据范围可知,n 为 2e6 , m 为 800 ,显然出题人是有意卡掉这样的暴力匹配
考虑优化,其实我们可以使用 bitset 优化暴力,可以使得时空复杂度降低 64 个常数,优化方法非常巧妙:
首先就是利用 bitset 数组来充当可行数组,设其为 b 数组,即 bitset<800>b[ 10 ],则 b [ i ][ j ] = 1 代表第 j 个位置上允许放置数字 i,然后利用一个 bitset 变量记录每一次的匹配状态,记这个变量为 cur,cur 的含义是:模式串遍历到位置 i 时,cur 的第 j 位的状态代表着 [ i - j + 1 , i ] 这段区间是否和匹配串的前 j 个字符匹配,注意 cur 的每一位都是相互独立的
每一次对于 cur 的操作是,令其左移一位,再将第一个位置置为 1 ,设当前遍历到的数字为 num ,则接下来就令 cur &= b[ num ] ,因为 b[ num ] 中存放的二进制为 1 的位置代表着可以放置字符 num 的位置,所以在进行 “ 或 ” 运算后,cur 中仍然为 1 的位置 j 表示在第 [ i - j + 1 , j - 1 ] 这段区间与匹配串的前 j - 1 个字符匹配的基础上,第 j 个字符仍然匹配,所以根据动态规划的思想可以巧妙地将状态转移为 cur 的第 j 个位置也为 1
综上所述,每次只要 cur 的第 m 个位置为 1 时,就说明当前连续的 m 个字符匹配成功,答案加一且将 cur 清空
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=2e6+100;
int a[N];
bitset<810>b[10],cur,pre;
int main()
{
#ifndef ONLINE_JUDGE
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%1d",a+i);
for(int i=1;i<=m;i++)
{
int k;
scanf("%d",&k);
while(k--)
{
int num;
scanf("%d",&num);
b[num][i]=1;
}
}
int ans=0;
for(int i=1;i<=n;i++)
{
pre<<=1;
pre[1]=1;
cur=pre&b[a[i]];
if(cur[m])
{
ans++;
cur.reset();
}
pre=cur;
}
if(ans==0)
puts("Failed to win the prize");
else
printf("%d\n",ans);
return 0;
}