【XSY3347】串字尾
原題:2018 ICPC Asia-East Continent Final J
想看原題解的可以去看吉老師的直播題解
題意:
題解:
(dllca膜你賽搬原題差評)
考慮題目中給出的式子的含義,實際上相當於要給串$s$的每個字尾分配一個概率$p_i$滿足$\sum\limits_{i=1}^{n}p_i=1$,然後取其中一個與其它字尾的lcp期望值最小的字尾,要做的就是求出一種最優的分配p的方案使得最後的最小值最大;
先不考慮字尾,考慮若干個lcp為0(即沒有公共字首)的字串如何分配最優:
設有$m$個串$s_1,s_2,...,s_m$,長度分別為$l_1,l_2,...,l_m$,那麼取其中一個串$s_t$對答案的貢獻是$p_tl_t$,最後的答案就是$min\{p_tl_t\}$;
一個結論:要使答案最大,所有$p_tl_t$的值必定相等;
如果不全相等,答案就是其中最小的那個,則必然可以通過調整$p$使得最小的那個增加一點,其他的全部減小一點,從而使答案更優;
於是可以列出一個方程組:
$$\begin{cases}\sum\limits_{i=1}^{n}p_i=1 \\ p_1l_1=p_2l_2=\cdots=p_nl_n=k\end{cases}$$
其中$k$就是答案,聯立兩式得:
$$k=\frac{1}{\frac{1}{l_1}+\frac{1}{l_2}+\cdots+\frac{1}{l_n}}$$
顯然就可以直接求出$k$了;
回到原問題,涉及到快速求字尾的lcp容易想到先構造出字尾樹,由於字尾樹本質上還是一棵trie樹,因此一個節點所有的後繼節點以及向下的子樹所表示的字串在這個點向後都是沒有公共字首的,所以可以用上面的方法來處理;
注意到其實某一個節點子樹中的問題是整個字尾樹上問題完全等價的子問題,因此可以在後綴樹上dfs,父節點直接加上子樹的答案繼續合併即可;
dfs的時候注意如果一個節點本身已經是原串某一個字尾的尾節點那麼它的後繼節點肯定沒有貢獻,直接返回0即可;
於是就做完了,講了這麼多程式碼還是很短的!
程式碼:
1 #include<algorithm>
2 #include<iostream>
3 #include<cstring>
4 #include<cstdio>
5 #include<cmath>
6 #include<queue>
7 #define inf 2147483647
8 #define eps 1e-9
9 using namespace std;
10 typedef long long ll;
11 typedef double db;
12 struct edge{
13 int v,next;
14 }a[1000001];
15 int t,n,tot,last,rt,tote,head[1000001],son[1000001][26],mx[1000001],fa[1000001],isp[1000001];
16 char s[200001];
17 void add(int u,int v){
18 a[++tote].v=v;
19 a[tote].next=head[u];
20 head[u]=tote;
21 }
22 void extend(int ch){
23 int p=last,np=++tot;
24 mx[np]=mx[p]+1;
25 for(;p&&!son[p][ch];p=fa[p])son[p][ch]=np;
26 if(!p)fa[np]=rt;
27 else{
28 int q=son[p][ch];
29 if(mx[q]==mx[p]+1)fa[np]=q;
30 else{
31 int nq=++tot;
32 mx[nq]=mx[p]+1;
33 memcpy(son[nq],son[q],sizeof(son[q]));
34 fa[nq]=fa[q];
35 fa[q]=fa[np]=nq;
36 for(;p&&son[p][ch]==q;p=fa[p])son[p][ch]=nq;
37 }
38 }
39 isp[np]=true;
40 last=np;
41 }
42 db dfs(int u){
43 if(isp[u])return 0;
44 db ret=0;
45 for(int tmp=head[u];tmp!=-1;tmp=a[tmp].next){
46 int v=a[tmp].v;
47 ret+=1.0/(dfs(v)+mx[v]-mx[u]);
48 }
49 return 1.0/ret;
50 }
51 int main(){
52 memset(head,-1,sizeof(head));
53 scanf("%d",&t);
54 while(t--){
55 scanf("%s",s);
56 n=strlen(s);
57 rt=last=++tot;
58 for(int i=n-1;i>=0;i--){
59 extend(s[i]-'a');
60 }
61 for(int i=rt+1;i<=tot;i++){
62 add(fa[i],i);
63 }
64 printf("%.10lf\n",dfs(rt));
65 }
66 return 0;
67 }