6708. 【2020.06.08省選模擬】密碼

題目


正解

比賽時幾乎沒有思考,直接放正解。

先講講GrayZhong的NB做法。
看着這題不難讓人想到FFT,然而這題要求概率,係數應該乘在一起,但直接卷積是加在一起的。
於是——取對數!
然後過了(不知道爲什麼精度沒有被卡)。

然後就是題解做法。
真的沒有想到,正解真的是卡精度相關……
如果概率小於12\frac{1}{2},連乘lg1e9\lg1e9次就卡到了精度範圍。
正解就是建立在這個基礎上的……
將每個位置出現概率最大的數字找出來,記爲sis_i。很顯然,這個位置其它的數字出現的概率都小於等於12\frac{1}{2}
枚舉每個位置ii,然後從這個位置開始兩個串進行匹配。先求lcplcp,得到的失配的位置把出現概率最大的數字,改成這個位置上對應的數字,然後繼續往後面做lcplcp。同時計算概率。
概率如果卡到了精度範圍,就可以直接退出。所以如果失配的位置超過了lg1e9\lg1e9,就超出了精度,直接退出。時間複雜度是O(nlg1e9)O(n \lg 1e9)的。

至於怎麼快速地計算乘積,先將si..i+m1s_{i..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;
}

正解

竟然有卡精度作爲核心的題目解法……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章