KMP+AC自动机
BZOJ4974 - 字符串大师
看到“循环节”会想到 KMP 某著名结论:循环节长度 \(i-nxt[i]\) . 显然这里给出了循环节长度,所以可以求出 \(nxt[i]=i-pre[i]\) .
- \(nxt[i]\neq 0\)
直接 \(s[i]=s[nxt[i]]\) .
- \(nxt[i]=0\)
这就意味着 \(s[i-1]\) 的任何一个 \(nxt\) 的下一个字符都和当前不同,所以往前一直跳 \(nxt\) ,并在可用字符集里去掉它的下一个位置。最后剩下的第一个字符就是字典序最小的方案。
Code
s[1]='a'; nxt[0]=-1;
for ( int i=2; i<=n; i++ )
if ( nxt[i] ) s[i]=s[nxt[i]];
else
{
memset( vis,0,sizeof(vis) );
for ( int j=nxt[i-1]; j>=0; j=nxt[j] ) vis[s[j+1]-'a']=1;
for ( int j=0; j<26; j++ )
if ( !vis[j] ) { s[i]='a'+j; break; }
}
LOJ2246 - 动物园
统计 \(nxt\) 的同时累加 \(cnt\) 表示方案数,然后再来一遍,如果发现超过一半了就直接跳 \(nxt\) 直到合法即可。
Code
for ( int i=2,j=0; i<=n; i++ )
{
while ( j && (s[i]!=s[j+1]) ) j=nxt[j];
if ( s[i]==s[j+1] ) j++;
nxt[i]=j; cnt[i]=cnt[j]+1;
}
for ( int i=2,j=0; i<=n; i++ )
{
while ( j && (s[i]!=s[j+1]) ) j=nxt[j];
if ( s[i]==s[j+1] ) j++;
while ( j>i/2 ) j=nxt[j];
ans=1ll*ans*(cnt[j]+1)%Mod;
}
BZOJ4560 字符串覆盖
显然 KMP 可以求出所有出现位置。
- 对于最大值,可以 \(n!\) 枚举放置顺序,每次选择位置开头离上一个结尾最近的即可。
- 对于最小值,首先去掉完全重合的,因为可以把它放在重合位置上,不影响结果。然后从长到短排序,DP。设 \(dp[S][i]\) 表示放置情况为 \(S\) ,最后一个位置是 \(i\) 的覆盖总长度。当最后一个覆盖 \(i\) 的串不相交,\(dp[S][i]=\min(dp[S'][k])\) ,用前缀和优化。相交,\(dp[S][i]=\min(dp[S'][j]+i-j)\) ,单调队列优化。
谢邀,已经写吐了。
Code
LOJ2004 硬币游戏
上次做这玩意儿是生成函数板题…… 所以有没有普及做法
Code
LOJ2507 Matching
LOJ 题面比 BZOJ 清晰到不知道哪里去了。
题意其实就是,定义两个串相等为其离散化之后完全相同,求 \(a\) 在 \(b\) 中相等的子串个数。
离散化可以转化为,一个位置之前比它小的个数。如果这个相等那么就是相同。然后KMP+树状数组维护这个东西即可。
Code
//Author: RingweEH
void KMP()
{
memset( tr,0,sizeof(tr) );
for ( int i=2,j=0; i<=n; i++ )
{
while ( Query(pos[i])!=val[j+1] )
{
for ( int k=i-j; k<i-nxt[j]; k++ ) Add(pos[k],-1); j=nxt[j];
}
if ( Query(pos[i])==val[j+1] ) Add(pos[i],1),j++;
nxt[i]=j;
}
}
int main()
{
//freopen( "exam.in","r",stdin );
n=read(); m=read();
for ( int i=1; i<=n; i++ ) a[i]=read(),pos[a[i]]=i;
for ( int i=1; i<=n; i++ ) val[i]=Query(pos[i]),Add(pos[i],1);
for ( int i=1; i<=m; i++ ) b[i]=read(),c[i]=b[i];
KMP(); sort(c+1,c+1+m); memset( tr,0,sizeof(tr) );
for ( int i=1,j=0; i<=m; i++ )
{
b[i]=lower_bound(c+1,c+1+m,b[i])-c;
while ( j==n || Query(b[i])!=val[j+1] )
{
for ( int k=i-j; k<i-nxt[j]; k++ ) Add(b[k],-1);
j=nxt[j];
}
if ( Query(b[i])==val[j+1] ) j++,Add(b[i],1);
if ( j==n ) ans[++num]=i-j+1;
}
printf("%d\n",num );
for ( int i=1; i<=num; i++ ) printf("%d ",ans[i] );
return 0;
}
P6125 有趣的游戏
先建出 AC 自动机,标记每个位置是否是终止态和每个串所对应的终止节点。然后对于这个自动机建转移矩阵。如果是终止节点就连概率为 \(1\) 的自环,否则按概率转移到其他点,第 \(i\) 个串的答案就是 \(trans[0][id[i]]\) ,其中 \(id[i]\) 是这个串对于的终止节点。
由于要保证精度所以要把转移矩阵自乘若干遍……
Code
//Author: RingweEH
struct Matrix{}trans;
void Insert( char *s,int x )
{
int l=strlen(s),p=1,ch;
for ( int i=0; i<l; i++ )
{
ch=s[i]-'A'+1;
if ( !tr[p][ch] ) tr[p][ch]=++tot;
p=tr[p][ch];
}
pos[p]=1,id[x]=p;
}
void GetFail()
{
int hd=0,tl=1; q[0]=1; fail[1]=0;
while ( hd<tl )
{
int nw=q[hd++];
for ( int i=1; i<=m; i++ )
{
if ( !tr[nw][i] ) continue; int nxt=fail[nw];
while ( !tr[nxt][i] ) nxt=fail[nxt];
fail[tr[nw][i]]=tr[nxt][i];
if ( pos[tr[nxt][i]] ) pos[tr[nw][i]]=1;
q[tl++]=tr[nw][i];
}
}
}
void GetTrans()
{
for ( int i=1; i<=tot; i++ )
if ( pos[i] ) trans.mat[i][i]=1;
else
for ( int j=1; j<=m; j++ )
{
int p=i;
while ( !tr[p][j] ) p=fail[p];
p=tr[p][j]; trans.mat[i][p]+=pro[j];
}
}
int main()
{
//freopen( "exam.in","r",stdin );
for ( int i=1; i<=26; i++ ) tr[0][i]=1;
scanf("%d%d%d",&n,&l,&m);
for ( int i=1,x,y; i<=m; i++ ) scanf("%d%d",&x,&y),pro[i]=(db)x/(db)y;
for ( int i=1; i<=n; i++ ) scanf("%s",s),Insert(s,i);
GetFail(); GetTrans();
for ( int i=1; i<=50; i++ ) trans=trans*trans;
for ( int i=1; i<=n; i++ ) printf("%.2lf\n",trans.mat[1][id[i]] );
return 0;
}
P4045 密码
离谱题。先考虑求方案数。对所有串建立AC自动机,然后在上面跑状压DP,记录长度、当前节点和已经用掉的模板串状态。随便转移一下就好了。
考虑求方案。如果有一个字符不属于其他任何一个字符串,那么就必然要乘上 \(26\) ……而方案数不大于 \(42\) ,要么就是所有给出字符串的拼接,要么就是只有一个字符(但是 \(n\ge 1\) ,所以只可能是拼接)。
然后就这么个 \(O(n!)\) 要判一大堆东西……出题人我谢谢你。
(还有一种方式是 DP 显然…… /tuu )
Code
//Author: RingweEH
namespace ACAuto
{
const int M=110;
int tr[M][26],fail[M],val[M],tot=0,q[110];
void Insert( char *s,int x )
{
int l=strlen(s),p=0,ch;
for ( int i=0; i<l; i++ )
{
ch=s[i]-'a';
if ( !tr[p][ch] ) tr[p][ch]=++tot;
p=tr[p][ch];
}
val[p]=1<<x;
}
void GetFail()
{
//queue<int> q;
int head=0,tail=0;
for ( int i=0; i<26; i++ )
if ( tr[0][i] ) q[++tail]=tr[0][i];//q.push(tr[0][i]);
while ( head<tail )
{
int nw=q[++head]; //q.pop();
for ( int i=0; i<26; i++ )
if( tr[nw][i] )
{
fail[tr[nw][i]]=tr[fail[nw]][i];
q[++tail]=tr[nw][i];//q.push(tr[nw][i]);
}
else tr[nw][i]=tr[fail[nw]][i];
val[nw]|=val[fail[nw]];
}
}
}
using namespace ACAuto;
namespace GetPath
{
bool vis[26][110][1<<10],g[26][110][1<<10],lim;
int que[26];
bool DFS( int i,int j,int S )
{
if ( i==len ) { vis[i][j][S]=1; return g[i][j][S]=(S==((1<<n)-1)); }
bool fl=0;
if ( vis[i][j][S] ) return g[i][j][S];
else vis[i][j][S]=1;
for ( int ch=0; ch<26; ch++ )
if ( tr[j][ch] ) fl|=DFS(i+1,tr[j][ch],S|val[tr[j][ch]]);
return g[i][j][S]=fl;
}
void Write( int i,int j,int S )
{
if ( !g[i][j][S] ) return;
if ( i==len )
{
for ( int ch=1; ch<=len; ch++ ) putchar(que[ch]+'a');
puts(""); return;
}
for ( int ch=0; ch<26; ch++ )
que[i+1]=ch,Write(i+1,tr[j][ch],S|val[tr[j][ch]]);
}
}
int main()
{
//freopen( "exam.in","r",stdin );
scanf("%d%d",&len,&n);
for ( int i=0; i<n; i++ ) scanf("%s",s),Insert(s,i);
GetFail(); dp[0][0][0]=1; int lim=(1<<n);
for ( int i=1; i<=len; i++ )
for ( int j=0; j<=tot; j++ )
for ( int S=0; S<lim; S++ )
if ( dp[i-1][j][S] )
for ( int k=0; k<26; k++ )
dp[i][tr[j][k]][S|val[tr[j][k]]]+=dp[i-1][j][S];
ll ans=0;
for ( int i=0; i<=tot; i++ ) ans+=dp[len][i][lim-1];
printf("%lld\n",ans );
if ( ans>42 ) return 0;
GetPath::DFS(0,0,0);
GetPath::Write(0,0,0);
return 0;
}
LOJ2180 魔法咒语
数 据 分 治
前面 \(L\leq 100\) 的可以 AC自动机上 DP;后面长度 \(\leq 2\) 需要矩乘。
调了半年……结论:写矩乘要写 *= 和 void ,不然矩阵一大就炸空间……

浙公网安备 33010602011771号