[POJ2226]Muddy Fields(二分圖匹配)
阿新 • • 發佈:2020-07-24
配合上一篇效果更佳--->字串學習筆記一
4.0 四、字典樹
定義
字典樹又稱單詞查詢樹,Trie樹,是一種樹形結構,是一種雜湊樹的變種。典型應用是用於統計,排序和儲存大量的字串(但不僅限於字串),所以經常被搜尋引擎系統用於文字詞頻統計。它的優點是:利用字串的公共字首來減少查詢時間,最大限度地減少無謂的字串比較,查詢效率比雜湊樹高。
實現
從百度百科瞟的圖
字典樹一般用一個二維陣列定義,\(tr[now][t]\)表示\(now\)節點的字元為\(t\)的兒子的編號
同時我們還要開一個數組\(cnt[now][t]\)表示該節點的個數
在某些情況下,我們還要記錄有幾個字串在該節點終結、該節點屬於第幾個字串等等
一般來說,字典樹支援兩種操作:插入和查詢
假如要插入某個單詞
一開始我們位於根節點,也就是\(0\)
接下來我們判斷根節點是否有某一個兒子\(ch\)
即\(tr[now][ch]\)是否等於\(0\)
如果等於\(0\),那我們再新開一個節點,否則把該節點的個數加一
查詢操作也是如此,我們就從根節點一路走下去
如果可以走完,說明該單詞存在,否則該單詞不存在
程式碼實現
以洛谷P2922為例
#include<bits/stdc++.h> using namespace std; const int maxn=2e7+5; char c[maxn]; int tr[maxn][3],cnt[maxn][3],tot,ed[maxn][3]; void ad(char s[]){ int len=strlen(s); int now=0; for(int i=0;i<len;i++){ int t=s[i]-'0'; if(tr[now][t]){ cnt[now][t]++; } else { tr[now][t]=++tot; cnt[now][t]=1; } if(i==len-1) ed[now][t]++; now=tr[now][t]; } } int cx(char s[]){ int len=strlen(s); int now=0,ans=0,js=0,jud=0,t; for(int i=0;i<len;i++){ t=s[i]-'0'; if(tr[now][t]){ js+=ed[now][t]; if(i!=len-1)now=tr[now][t]; } else { jud=1; break; } } if(jud) return js; else return js-ed[now][t]+cnt[now][t]; } char s[maxn]; int main(){ int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ int t; scanf("%d",&t); int aa; for(int j=1;j<=t;j++){ scanf("%d",&aa); s[j-1]=aa+'0'; } s[t]='\0'; ad(s); } for(int i=1;i<=m;i++){ int t; scanf("%d",&t); int aa; for(int j=1;j<=t;j++){ scanf("%d",&aa); s[j-1]=aa+'0'; } s[t]='\0'; printf("%d\n",cx(s)); } return 0; }
5.0 五、習題總結
洛谷 P1659 [國家集訓隊]拉拉隊排練
題目描述
分析
這一道題的大致意思就是讓你求出一個字串中所有的奇迴文串,並把它們的長度連乘
考慮到求迴文串,我們要使用\(manacher\)演算法
因為題目中只讓你求出奇迴文串的個數,因此我們不用在原來的字元之間再插入特殊字元
在進行求解的時候,我們要使用一個\(p[i]\)陣列記錄以\(i\)為中心的最大回文半徑的長度
而對於一個位置\(i\),如果向兩邊擴充套件\(p[i]\)是一個迴文串,那麼向兩邊擴充套件\(p[i]-k(p[i]-k\geq 1)\)也是一個迴文串
因此,我們就可以求出以\(i\)為中心的所有迴文半徑的長度
但是,如果我們使用\(for\)
因此,我們可以使用差分陣列解決這一個問題,即在\(1\)的位置加一,在\(2 \times p[i] -1\)的位置減一
最後\(O(n)\)掃一遍即可
還有要注意的一點是,在進行乘法的時候,因為\(k\)的範圍很大,所以要使用快速冪
程式碼
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e6+5;
char s[maxn];
int p[maxn],k,len,cf[maxn];
const int mod=19930726;
int ksm(int ds,int zs){
int ans=1;
while(zs){
if(zs&1) ans=ans*ds%mod;
ds=ds*ds%mod;
zs>>=1;
}
return ans;
}
signed main(){
scanf("%lld%lld",&len,&k);
scanf("%s",s+1);
s[0]='$';
for(int i=1,r=0,mids=0;i<=len;i++){
if(i<=r) p[i]=min(p[2*mids-i],r-i+1);
while(s[i-p[i]]==s[i+p[i]]) p[i]++;
if(p[i]+i>r) r=p[i]+i-1,mids=i;
cf[1]++,cf[p[i]*2]--;
}
for(int i=1;i<=len;i++){
cf[i]=cf[i-1]+cf[i];
}
int mans=1,tot=len;
if(tot%2==0) tot--;
while(k>0 && tot>0){
mans=mans*ksm(tot,min(cf[tot],k))%mod;
k-=cf[tot];
tot-=2;
}
if(k>0) printf("-1\n");
else printf("%lld\n",mans);
return 0;
}
SP15569 STC02 - Antisymmetry
題目描述
分析
題意:對於一個只含有\(0\)和\(1\)的字串,求出其在異或意義下的迴文字串的數量
比較裸的\(manacher\),將判斷的條件稍微改一下即可
程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
char s1[maxn],s[maxn];
int f[maxn];
int main(){
int n;
scanf("%d",&n);
scanf("%s",s1+1);
s[0]='*';
int cnt=2*n+1;
for(int i=1;i<=cnt;i++){
if(i&1) s[i]='%';
else s[i]=s1[i/2];
}
int ans=0,mids=0,r=0;
for(int i=1;i<=cnt;i++){
if(i<=r) f[i]=min(f[2*mids-i],r-i+1);
while( ( (i-f[i])%2==0 && ( ((s[i+f[i]]-'0')^(s[i-f[i]]-'0')==1) ) )|| ( (i-f[i])%2==1 && (s[i-f[i]]==s[i+f[i]]) ) ) f[i]++;
if(i+f[i]>r) r=i+f[i]-1,mids=i;
if(i%2==1)ans+=((f[i]-1)/2);
}
printf("%d\n",ans);
return 0;
}
[SHOI2011]雙倍迴文
題目描述
分析
巧妙地利用了\(manacher\)演算法的性質,即通過對稱性查找回文字串
程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+15;
char s1[maxn],s[maxn];
int f[maxn];
int main(){
int n;
scanf("%d",&n);
scanf("%s",s1+1);
s[0]='*';
int cnt=2*n+1;
for(int i=1;i<=cnt;i++){
if(i&1) s[i]='%';
else s[i]=s1[i/2];
}
int ans=0,mids=0,r=0;
for(int i=1;i<=cnt;i+=2){
if(i<=r) f[i]=min(f[2*mids-i],r-i+1);
while(s[i+f[i]]==s[i-f[i]]) f[i]++;
if(i+f[i]-1>r) r=i+f[i]-1,mids=i;
if(i<r && i-f[i]<mids) ans=max(ans,2*(i-mids));
}
printf("%d\n",ans);
return 0;
}