题目
正解
比赛时几乎没有思考,直接放正解。
先讲讲GrayZhong的NB做法。
看着这题不难让人想到FFT,然而这题要求概率,系数应该乘在一起,但直接卷积是加在一起的。
于是——取对数!
然后过了(不知道为什么精度没有被卡)。
然后就是题解做法。
真的没有想到,正解真的是卡精度相关……
如果概率小于
1
2
\frac{1}{2}
21,连乘
lg
1
e
9
\lg1e9
lg1e9次就卡到了精度范围。
正解就是建立在这个基础上的……
将每个位置出现概率最大的数字找出来,记为
s
i
s_i
si。很显然,这个位置其它的数字出现的概率都小于等于
1
2
\frac{1}{2}
21。
枚举每个位置
i
i
i,然后从这个位置开始两个串进行匹配。先求
l
c
p
lcp
lcp,得到的失配的位置把出现概率最大的数字,改成这个位置上对应的数字,然后继续往后面做
l
c
p
lcp
lcp。同时计算概率。
概率如果卡到了精度范围,就可以直接退出。所以如果失配的位置超过了
lg
1
e
9
\lg1e9
lg1e9,就超出了精度,直接退出。时间复杂度是
O
(
n
lg
1
e
9
)
O(n \lg 1e9)
O(nlg1e9)的。
至于怎么快速地计算乘积,先将 s i . . i + m − 1 s_{i..i+m-1} si..i+m−1乘起来,对于失配的位置分别除去原来的乘上新的。时间比题解的猫树快到不知道哪里去了。
代码
好久没有写过后缀数组了……
以后就把这个当板子。之前写的博客太废了。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define N 200010
#define db double
int input(){
char ch=getchar();
while (ch<'0' || ch>'9')
ch=getchar();
int x=0;
do{
x=x*10+ch-'0';
ch=getchar();
}
while ('0'<=ch && ch<='9');
return x;
}
int n,m,len;
db p[N][10];
int s[N],t[N];
char _t[N];
int str[N*2];
int sa[N*2],rk[N*2],height[N*2];
void initSA(int s[],int n){
static int buc[N*2],sa2[N*2],rk2[N*2];
int S=11;
for (int i=1;i<=n;++i)
buc[rk[i]=s[i]+1]++;
for (int i=1;i<=S;++i)
buc[i]+=buc[i-1];
for (int i=n;i>=1;--i)
sa[buc[rk[i]]--]=i;
for (int i=1;i==1 || S<n;i<<=1){
int cnt=0;
for (int j=n-i+1;j<=n;++j)
sa2[++cnt]=j;
for (int j=1;j<=n;++j)
if (sa[j]>i)
sa2[++cnt]=sa[j]-i;
for (int j=1;j<=n;++j)
rk2[j]=rk[sa2[j]];
memset(buc,0,sizeof(int)*(S+1));
for (int j=1;j<=n;++j)
buc[rk2[j]]++;
for (int j=1;j<=S;++j)
buc[j]+=buc[j-1];
for (int j=n;j>=1;--j)
sa[buc[rk2[j]]--]=sa2[j];
S=1;
rk2[sa[1]]=1;
for (int j=2;j<=n;++j)
rk2[sa[j]]=(rk[sa[j]]==rk[sa[j-1]] && (sa[j]+i<=n?rk[sa[j]+i]==rk[sa[j-1]+i]:0)?S:++S);
memcpy(rk,rk2,sizeof(int)*(n+1));
}
for (int i=1,k=0;i<=n;++i)
if (rk[i]<n){
if (k) --k;
int j=sa[rk[i]+1];
while (i+k<=n && j+k<=n && s[i+k]==s[j+k])
++k;
height[rk[i]]=k;
}
}
int lg[2*N],f[2*N][20];
inline int query(int l,int r){
int m=lg[r-l+1];
return min(f[l][m],f[r-(1<<m)+1][m]);
}
inline int lcp(int x,int y){
y+=n+1;
if (rk[x]>rk[y])
swap(x,y);
return query(rk[x],rk[y]-1);
}
int main(){
freopen("password.in","r",stdin);
freopen("password.out","w",stdout);
n=input(),m=input();
for (int i=1;i<=n;++i){
s[i]=0;
for (int j=0;j<=9;++j){
int x=input();
p[i][j]=x/1e9;
s[i]=(p[i][s[i]]<p[i][j]?j:s[i]);
}
}
scanf("%s",_t+1);
for (int i=1;i<=m;++i)
t[i]=_t[i]-'0';
for (int i=1;i<=n;++i)
str[i]=s[i];
str[n+1]=10;
for (int i=1;i<=m;++i)
str[n+1+i]=t[i];
initSA(str,len=n+m+1);
for (int i=1;i<len;++i)
f[i][0]=height[i];
for (int i=1;1+(1<<i)<len;++i)
for (int j=1;j+(1<<i)<len;++j)
f[j][i]=min(f[j][i-1],f[j+(1<<i-1)][i-1]);
lg[1]=0;
for (int i=2;i<=len;++i)
lg[i]=lg[i>>1]+1;
db pro=1;
for (int i=1;i<=m;++i)
pro*=p[i][s[i]];
for (int i=1;i+m-1<=n;++i){
db tmp=pro;
int x=i-1,y=0;
while (tmp>1e-9){
int k=lcp(x+1,y+1);
x+=k,y+=k;
if (y==m)
break;
tmp=tmp/p[x+1][s[x+1]]*p[x+1][t[y+1]];
x++,y++;
}
printf("%.10lf\n",tmp);
pro=pro/p[i][s[i]]*p[i+m][s[i+m]];
}
return 0;
}
正解
竟然有卡精度作为核心的题目解法……