bzoj 2553 [BeiJing2011]禁忌
内容
卡精度吃枣药丸
Description
Magic Land上的人们总是提起那个传说:他们的祖先John在那个东方岛屿帮助Koishi与其姐姐Satori最终战平。而后,Koishi恢复了读心的能力……
如今,在John已经成为传说的时代,再次造访那座岛屿的人们却发现Koishi遇到了新麻烦。
这次她遇到了Flandre Scarlet——她拥有可以使用禁忌魔法而不会受到伤害的能力。
为了说明什么是禁忌魔法及其伤害,引入以下概念:
1.字母集A上的每个非空字符串对应了一个魔法。
其中A是包含了前alphabet个小写字母的集合。
2.有一个集合T,包含了N个字母集A上的字符串
T中的每一串称为一个禁忌串(Taboo string)
3.一个魔法,或等价地,其对应的串s因为包含禁忌而对使用者造成的伤害按以下方式确定:
把s分割成若干段,考虑其中是禁忌串的段的数目,不同的分割可能会有不同的数目,其最大值就是这个伤害。
由于拥有了读心的能力,Koishi总是随机地使用Flandre Scarlet的魔法,可以确定的是,她的魔法正好对应字母集A上所有长度为len的串。
但是,Flandre Scarlet所使用的一些魔法是带有禁忌的,由于其自身特性,她可以使用禁忌魔法而不受到伤害,而Koishi就不同了。可怜的Koishi每一次使用对方的魔法都面临着受到禁忌伤害的威胁。
你现在需要计算的是如果Koishi使用对方的每一个魔法的概率是均等的,那么每一次随机使用魔法所受到的禁忌伤害的期望值是多少。
Input
第一行包含三个正整数N、len、alphabet。
接下来N行,每行包含一个串Ti,表示禁忌串。
Output
一个非负实数,表示所受到禁忌伤害的期望值。
Sample Input
2 4 2
aa
abb
Sample Output
0.75
【样例1解释】
一共有2^4 = 16种不同的魔法。
需要注意的是“aabb”的禁忌伤害是1而不是2。
HINT
100%的数据中N ≤ 5,len ≤109,1 ≤ alphabet ≤ 26。
在所有数据中,有不少于40%的数据中:N = 1。
数据保证每个串Ti的长度不超过15,并且不是空串。
数据保证每个Ti均仅含有前alphabet个小写字母。
数据保证集合T中没有相同的元素,即对任意不同的i和j,有Ti≠Tj。
【评分方法】
对于每一组数据,如果没有得到正确的输出(TLE、MLE、RTE、输出格式错误等)得0分。
否则:设你的输出是YourAns,标准输出是StdAns:
记MaxEPS = max(1.0 , StdAns)×10-6
如果|YourAns – StdAns| ≤ MaxEPS则得10分,否则得0分。
即:你的答案需要保证相对误差或绝对误差不超过10-6。
题解
我卡了一晚上的精度= =都要吐了
数据范围绝对是矩乘,先想下纯的dp怎么写。
首先禁忌串建出AC自动机,f[i][j]表示长度为i到j节点的概率。每次到is_end的时候更新答案,把概率强制怼到根上去。
#include <cstdio>
#include <cstring>
#include <queue>
#define ll long long
#define N 2010
using namespace std;
int n,m,sz,al,l,k,root,ch[N][26],is_end[N],fail[N];
double f[N][N],ans;
char s[N];
void insert(char s[])
{
int len=strlen(s+1),now=root;
for (int i=1;i<=len;now=ch[now][s[i]-'a'],i++)
if (!ch[now][s[i]-'a']) ch[now][s[i]-'a']=++sz;
is_end[now]=1;
}
void make_fail()
{
queue<int>que;
for (int i=0;i<=9;i++)
if (ch[root][i]) que.push(ch[root][i]);
while(!que.empty())
{
int now=que.front();que.pop();
for (int i=0;i<al;i++)
if (ch[now][i])
is_end[ch[now][i]]|=is_end[ch[fail[now]][i]],
fail[ch[now][i]]=ch[fail[now]][i],
que.push(ch[now][i]);
else
ch[now][i]=ch[fail[now]][i];
}
}
main()
{
scanf("%d%d%d",&n,&l,&al);
for (int i=1;i<=n;i++)
scanf("%s",s+1),insert(s);
make_fail();
f[0][root]=1;
for (int i=1;i<=l;i++)
for (int j=0;j<=sz;j++)
if (f[i-1][j])
for (int k=0;k<al;k++)
if (!is_end[ch[j][k]])
f[i][ch[j][k]]+=f[i-1][j]/(double)al;
else
f[i][root]+=f[i-1][j]/(double)al,ans+=f[i-1][j]/(double)al;
printf("%.5lf\n",ans);
}
对拍无误。。
考虑怎么矩阵,发现怼到根上的操作很皮啊(主要是更新ans),根本没法转。那么就新建一个节点,所有的is_end节点都连向它,它再连到根上,那么就在这个新点统计答案就ok了
最后
开long double!!!!!!!!
#include <cstdio>
#include <cstring>
#include <queue>
#define ll long long
#define N 110
using namespace std;
int n,sz,al,l,k,root,ch[N][26],is_end[N],fail[N],vis[N];
char s[N];
struct matrixs
{
long double m[N][N];
void clear(){for (int i=0;i<=n;i++) for (int j=0;j<=n;j++) m[i][j]=0.0;}
matrixs operator * (const matrixs &a) const
{
matrixs ans={};
for (int i=0;i<=n;i++)
for (int k=0;k<=n;k++)
if (m[i][k])
for (int j=0;j<=n;j++)
ans.m[i][j]+=m[i][k]*a.m[k][j];
return ans;
}
matrixs matrix_pow(int p)
{
matrixs ans,a=(*this);ans.clear();
for (int i=0;i<=n;i++) ans.m[i][i]=1;
for (;p;a=a*a,p>>=1)
if (p&1) ans=ans*a;
return ans;
}
};
void insert(char s[])
{
int len=strlen(s+1),now=root;
for (int i=1;i<=len;now=ch[now][s[i]-'a'],i++)
if (!ch[now][s[i]-'a']) ch[now][s[i]-'a']=++sz;
is_end[now]=1;
}
void make_fail()
{
queue<int>que;
for (int i=0;i<al;i++)
if (ch[root][i]) que.push(ch[root][i]);
while(!que.empty())
{
int now=que.front();que.pop();
for (int i=0;i<al;i++)
if (ch[now][i])
fail[ch[now][i]]=ch[fail[now]][i],
que.push(ch[now][i]);
else
ch[now][i]=ch[fail[now]][i];
is_end[now]|=is_end[fail[now]];
}
}
matrixs m;
void build_m()
{
queue<int>que;
que.push(root);vis[root]=1;
long double unit=1.0/(long double)al;
while(!que.empty())
{
int now=que.front();que.pop();
for (int i=0;i<al;i++)
{
if (!vis[ch[now][i]]) vis[ch[now][i]]=1,que.push(ch[now][i]);
if (is_end[ch[now][i]]) m.m[now][n]+=unit,m.m[now][root]+=unit;
else m.m[now][ch[now][i]]+=unit;
}
}
}
main()
{
scanf("%d%d%d",&n,&l,&al);
for (int i=1;i<=n;i++)
scanf("%s",s+1),insert(s);
make_fail();
n=sz+1;
build_m();
m.m[n][n]=1;
matrixs ans=m.matrix_pow(l);
printf("%.7lf\n",(double)ans.m[0][n]);
}