日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

NOI 2016 优秀的拆分 (后缀数组+差分)

發(fā)布時間:2023/11/30 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 NOI 2016 优秀的拆分 (后缀数组+差分) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

題目大意:給你一個字符串,求所有子串的所有優(yōu)秀拆分總和,優(yōu)秀的拆分被定義為一個字符串可以被拆分成4個子串,形如$AABB$,其中$AA$相同,$BB$相同,$AB$也可以相同

作為一道國賽題,95分竟然就這么給我們了!只是一個$NOIP$難度的哈希套$DP$啊......

95分就是從后往前找,統(tǒng)計$AA$串,每次統(tǒng)計一下從這個位置開始的所有子串 和 緊隨其后的等長串 相同的個數(shù)$sum$

$hash(i,i+j-1)==hash(i+j,i+2*j-1) sum[i]++$

然后再統(tǒng)計$BB$串,就是加上在這兩個串之后的位置的$sum$值,這樣就統(tǒng)計出了合法拆分數(shù)

$dp[i]+=sum[i+2*j]$

95分就這樣到手啦,竟然連自然溢出hash都不卡

1 #include <cmath> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #define ll long long 6 #define ull unsigned long long 7 #define N 2050 8 #define seed 233 9 #define idx(x) (x-'a'+1) 10 using namespace std; 11 //re 12 int T,len; 13 char str[N]; 14 int dp[N],sum[N]; 15 ull hsh[N],sp[N]; 16 ull ghsh(int x,int y) {return hsh[y]-hsh[x-1]*sp[y-x+1];} 17 void clr() 18 { 19 memset(dp,0,sizeof(dp)); 20 memset(sp,0,sizeof(sp)); 21 memset(sum,0,sizeof(sum)); 22 memset(hsh,0,sizeof(hsh)); 23 } 24 int main() 25 { 26 scanf("%d",&T); 27 while(T--) 28 { 29 clr(); 30 scanf("%s",str+1); 31 len=strlen(str+1); 32 sp[0]=1; 33 for(int i=1;i<=len;i++) 34 hsh[i]=hsh[i-1]*seed+idx(str[i]), 35 sp[i]=sp[i-1]*seed; 36 for(int i=len-1;i>=1;i--) 37 { 38 for(int j=1;i+2*j-1<=len;j++) 39 { 40 if(ghsh(i,i+j-1)==ghsh(i+j,i+2*j-1)){ 41 dp[i]++; 42 sum[i]+=dp[i+2*j]; 43 } 44 } 45 } 46 int ans=0; 47 for(int i=1;i<=len;i++) 48 ans+=sum[i]; 49 printf("%d\n",ans); 50 } 51 return 0; 52 }

接下來才是本題的重點!$NOI$豈是你想$AK$就$AK$的!出題人可能是想針對某位巨佬

不看題解這個正解思路真是很難想到......

思路大概是這樣的,其實我們每次只需要統(tǒng)計$AA$串的數(shù)量就行了,因為$AABB$串的數(shù)量等于,在$i-1$位結(jié)束的$AA$串的數(shù)量乘以在第$i$位開始的$AA$串的數(shù)量,用公式表示就是

接下來就是統(tǒng)計$st$和$ed$了,感覺正解的思路很神

先用后綴數(shù)組+$ST$表處理出任意兩個位置,作為后綴串開頭的$LCP$以及作為前綴串末尾的$LCS$,求$LCS$可以把串反著讀再去套$SA$

每次選取一個$A$串的長度$len$,再在原串每隔$len$的位置設(shè)一個關(guān)鍵點

然后會發(fā)現(xiàn)一個神奇的性質(zhì),任意一個長度為$2*len$的串必然經(jīng)過且僅經(jīng)過2個關(guān)鍵點!

接下來統(tǒng)計經(jīng)過所有長度為$2*len$所有$AA$串,比如選定兩個關(guān)鍵點$a,b$,且$a+len=b$

如果把$a$的和b$相同的前綴和后綴拼在一起,且拼完之后長度>=len,說明存在至少一個$AA$串

可以把$AA$串想象成一個塊,在兩個關(guān)鍵點上移動,塊的左端點不能大于$a$,塊的右端點不能小于$b$,塊不能移動到上一個或者下一個關(guān)鍵點的位置,塊內(nèi)必須是$AA$串,這樣,塊所有能移動的位置-塊的長度$len$,就是經(jīng)過$ab$兩個關(guān)鍵點的所有$AA$串數(shù)

時間變成,$logn$是調(diào)和級數(shù)的近似值

細節(jié)比較多需要仔細思考

1 #include <cmath> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #define ll long long 6 #define N 30100 7 #define seed 233 8 #define idx(x) (x-'a'+1) 9 using namespace std; 10 //re 11 int T,len; 12 int lg[N]; 13 ll st[N],ed[N],S[N],E[N]; 14 struct suffix{ 15 char str[N]; 16 int sa[N],tr[N],rk[N],hs[N],h[N],f[N][15],len; 17 bool check(int k,int x,int y){ 18 if(x+k>len||y+k>len) return 0; 19 else return (rk[x]==rk[y]&&rk[x+k]==rk[y+k])?1:0; 20 } 21 void get_suffix() 22 { 23 int cnt=0,i;len=strlen(str+1); 24 for(i=1;i<=len;i++) hs[str[i]]++; 25 for(i=1;i<=127;i++) if(hs[i]) tr[i]=++cnt; 26 for(i=1;i<=127;i++) hs[i]+=hs[i-1]; 27 for(i=1;i<=len;i++) rk[i]=tr[str[i]],sa[hs[str[i]]--]=i; 28 for(int k=1;cnt<len;k<<=1) 29 { 30 for(i=1;i<=cnt;i++) hs[i]=0; 31 for(i=1;i<=len;i++) hs[rk[i]]++; 32 for(i=1;i<=cnt;i++) hs[i]+=hs[i-1]; 33 for(i=len;i>=1;i--) if(sa[i]>k) tr[sa[i]-k]=hs[rk[sa[i]-k]]--; 34 for(i=1;i<=k;i++) tr[len-i+1]=hs[rk[len-i+1]]--; 35 for(i=1;i<=len;i++) sa[tr[i]]=i; 36 for(i=1,cnt=0;i<=len;i++) tr[sa[i]]=check(k,sa[i],sa[i-1])?cnt:++cnt; 37 for(i=1;i<=len;i++) rk[i]=tr[i]; 38 } 39 for(i=1;i<=len;i++) 40 { 41 if(rk[i]==1) continue; 42 for(int j=max(1,h[rk[i-1]]-1);;j++) 43 if(str[i+j-1]==str[sa[rk[i]-1]+j-1]) h[rk[i]]=j; 44 else break; 45 } 46 } 47 void get_ST() 48 { 49 for(int i=1;i<=len;i++) 50 f[i][0]=h[i]; 51 for(int k=1;(1<<k)<=len;k++) 52 for(int i=1;i+(1<<k)-1<=len;i++) 53 f[i][k]=min(f[i][k-1],f[i+(1<<(k-1))][k-1]); 54 } 55 int query(int x,int y) 56 {int L=y-x+1; 57 return min(f[x][lg[L]],f[y-(1<<lg[L])+1][lg[L]]);} 58 }p,s; 59 void clr() 60 { 61 memset(S,0,sizeof(S)),memset(st,0,sizeof(st)); 62 memset(E,0,sizeof(E)),memset(ed,0,sizeof(ed)); 63 memset(&p,0,sizeof(p)),memset(&s,0,sizeof(s)); 64 } 65 int main() 66 { 67 scanf("%d",&T); 68 for(int i=2;i<=30010;i++) 69 lg[i]=lg[i>>1]+1; 70 while(T--) 71 { 72 clr(); 73 scanf("%s",s.str+1); 74 int len=strlen(s.str+1); 75 for(int i=1;i<=len;i++) 76 p.str[i]=s.str[len-i+1]; 77 s.get_suffix(); 78 s.get_ST(); 79 p.get_suffix(); 80 p.get_ST(); 81 int sx,sy,px,py,ss,sp,l,r; 82 for(int i=1;i<=len;i++) 83 { 84 for(int j=i+1;j<=len;j+=i) 85 { 86 sx=s.rk[j-i+1],sy=s.rk[j+1]; 87 if(sx>sy) swap(sx,sy); 88 px=p.rk[len-(j-i)+1],py=p.rk[len-j+1]; 89 if(px>py) swap(px,py); 90 ss=s.query(sx+1,sy); 91 sp=p.query(px+1,py); 92 if(ss+sp<i||sp==0) continue; 93 l=max(j-i-i+1,j-i-sp+1); 94 r=min(j+i-1,j+ss); 95 S[l]++,S[r-2*i+2]--; 96 E[l+2*i-1]++,E[r+1]--; 97 } 98 } 99 for(int i=1;i<=len;i++) 100 st[i]=st[i-1]+S[i],ed[i]=ed[i-1]+E[i]; 101 ll ans=0; 102 for(int i=2;i<=len;i++) 103 ans+=ed[i-1]*st[i]; 104 printf("%lld\n",ans); 105 } 106 return 0; 107 }

?

轉(zhuǎn)載于:https://www.cnblogs.com/guapisolo/p/9707026.html

總結(jié)

以上是生活随笔為你收集整理的NOI 2016 优秀的拆分 (后缀数组+差分)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。