1.字符串专题
字符串专题
\(A\) CF1037H Security
\(B\) CF1073G Yet Another LCP Problem
-
多倍经验: luogu P7409 SvT
-
把 \(a\) 和 \(b\) 拼起来得到 \(c\) ,由容斥有 \(\sum\limits_{i=1}^{k}\sum\limits_{j=1}^{l}LCP(s_{a_{i} \sim n},s_{b_{j} \sim n})=\frac{\sum\limits_{i=1}^{k+l}\sum\limits_{j=1}^{k+l}LCP(s_{c_{i} \sim n},s_{c_{j} \sim n})-\sum\limits_{i=1}^{k}\sum\limits_{j=1}^{k}LCP(s_{a_{i} \sim n},s_{a_{j} \sim n})-\sum\limits_{i=1}^{l}\sum\limits_{j=1}^{l}LCP(s_{b_{i} \sim n},s_{b_{j} \sim n})}{2}\) 。
-
以 \(a\) 为例,先将 \(a\) 按照 \(rk\) 排序,由 \(sa_{rk_{a_{i}}}=a_{i}\) 可以构造 \(\begin{aligned} g_{i}=LCP(a_{i-1},a_{i})=\begin{cases} n-a_{i}+1 &a_{i-1}=a_{i} \\ \min\limits_{j=rk_{a_{i-1}+1}}^{rk_{a_{i}}} \{ height_{j} \} &a_{i-1} \ne a_{i} \end{cases} \end{aligned}\) ,特别的,有 \(g_{1}=0\) 。此时有 \(\begin{aligned} \sum\limits_{i=1}^{k}\sum\limits_{j=1}^{k}\frac{LCP(s_{a_{i} \sim n},s_{a_{j} \sim n})}{2} &=\sum\limits_{i=1}^{k}\sum\limits_{j=i+1}^{k}LCP(s_{a_{i} \sim n},s_{a_{j} \sim n})+\sum\limits_{i=1}^{k}\frac{LCP(s_{a_{i} \sim n},s_{a_{i} \sim n})}{2} \\ &=\sum\limits_{i=1}^{k}\sum\limits_{j=i+1}^{k}\min\limits_{h=rk_{a_{i}}+1}^{rk_{a_{j}}} \{ height_{h} \}+\sum\limits_{i=1}^{k}\frac{n-a_{i}+1}{2} \\ &=\sum\limits_{i=1}^{k}\sum\limits_{j=i+1}^{k}\min\limits_{h=i+1}^{j} \{ g_{h} \}+\sum\limits_{i=1}^{k}\frac{n-a_{i}+1}{2} \\ &=\sum\limits_{i=2}^{k}\sum\limits_{j=i}^{k}\min\limits_{h=i}^{j} \{ g_{h} \}+\sum\limits_{i=1}^{k}\frac{n-a_{i}+1}{2} \\ &=\sum\limits_{i=1}^{k}\sum\limits_{j=i}^{k}\min\limits_{h=i}^{j} \{ g_{h} \}-\sum\limits_{i=1}^{k}0+\sum\limits_{i=1}^{k}\frac{n-a_{i}+1}{2} \\ &=\sum\limits_{i=1}^{k}\sum\limits_{j=i}^{k}\min\limits_{h=i}^{j} \{ g_{h} \}+\sum\limits_{i=1}^{k}\frac{n-a_{i}+1}{2} \end{aligned}\) 。
-
\(g_{i}\) 可以使用 \(ST\) 表预处理,然后类似 luogu P6503 [COCI2010-2011#3] DIFERENCIJA 使用单调栈维护即可。
点击查看代码
ll sa[400010],rk[800010],oldrk[800010],id[400010],cnt[400010],key[400010],height[400010],a[400010],b[400010],c[400010],fminn[400010][20],f[400010],g[400010]; char s[400010]; bool cmp(ll a,ll b) { return rk[a]<rk[b]; } ll val(char x) { return (ll)x; } void counting_sort(ll n,ll m) { memset(cnt,0,sizeof(cnt)); for(ll i=1;i<=n;i++) { cnt[key[i]]++; } for(ll i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(ll i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init_sa(char s[],ll len) { ll m=127,tot=0,num=0,i,j,w; for(i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(w=1;tot!=len;w<<=1,m=tot) { num=0; for(i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } for(i=1,j=0;i<=len;i++) { j-=(j>=1); while(s[i+j]==s[sa[rk[i]-1]+j]) { j++; } height[rk[i]]=j; } } void init(ll n,ll a[]) { memset(fminn,0x3f,sizeof(fminn)); for(ll i=1;i<=n;i++) { fminn[i][0]=a[i]; } for(ll j=1;j<=log2(n);j++) { for(ll i=1;i<=n-(1<<j)+1;i++) { fminn[i][j]=min(fminn[i][j-1],fminn[i+(1<<(j-1))][j-1]); } } } ll query(ll l,ll r) { ll t=log2(r-l+1); return min(fminn[l][t],fminn[r-(1<<t)+1][t]); } ll ask(ll n,ll k,ll a[]) { ll sum=0,i; stack<ll>st; sort(a+1,a+1+k,cmp); for(i=1;i<=k;i++) { g[i]=((a[i]==a[i-1])?n-a[i]+1:query(rk[a[i-1]]+1,rk[a[i]])); sum+=(n-a[i]+1)/2;//其实这里这样写是不太严谨的,但这一行的可以互相消去的,所以就这样写了 } for(i=1;i<=k;i++) { while(st.empty()==0&&g[st.top()]>=g[i]) { st.pop(); } if(st.empty()==0) { f[i]=f[st.top()]+g[i]*(i-st.top()); } else { f[i]=f[0]+g[i]*(i-0); } st.push(i); sum+=f[i]; } return sum; } int main() { ll n,m,k,l,i,j; cin>>n>>m>>(s+1); init_sa(s,n); init(n,height); for(j=1;j<=m;j++) { cin>>k>>l; for(i=1;i<=k;i++) { cin>>a[i]; c[i]=a[i]; } for(i=1;i<=l;i++) { cin>>b[i]; c[i+k]=b[i]; } cout<<ask(n,k+l,c)-ask(n,k,a)-ask(n,l,b)<<endl; } return 0; }
\(C\) CF906E Reverses
\(D\) CF666E Forensic Examination
\(E\) luogu P4199 万径人踪灭
- 因为需要 \(FFT\) ,所以被 \(miaomiao\) 毙了。
\(F\) CF1535F String Distance
-
考虑 \(f(s_{i},s_{j})\) 的取值,题目中已保证 \(|s_{i}|=|s_{j}|\) 。
- 当 \(s_{i}\) 和 \(s_{j}\) 字符集不同时,无法使 \(s_{i},s_{j}\) 相等,故 \(f(s_{i},s_{j})=1337\) 。
- 当 \(s_{i}\) 和 \(s_{j}\) 字符集相同,且 \(s_{i}=s_{j}\) 时,有 \(f(s_{i},s_{j})=0\) 。
- 因保证字符串两两不相同,可以不考虑这种情况。
- 当 \(s_{i}\) 和 \(s_{j}\) 字符集相同,且 \(s_{i} \ne s_{j}\) ,存在一组 \(l,r(1 \le l<r \le |s_{i}|=|s_{j}|)\) 使得 \(\begin{cases} s_{i,1 \sim l-1}=s_{j,1 \sim l-1} \\ s_{i,l \sim r} \ne s_{j,l \sim r} \\ \exists k \in \{ i,j \},s_{k,l} \le s_{k,l+1} \le s_{k,l+2} \le \dots \le s_{k,r} \\ s_{i, r+1 \sim |s_{i}|}=s_{j,r+1 \sim |s_{j}|} \end{cases}\) 时,对 \(s_{i \bigoplus j \bigoplus k}\) 的 \([l,r]\) 进行一次排序即可,故 \(f(s_{i},s_{j})=1\) 。
- 当 \(s_{i}\) 和 \(s_{j}\) 字符集相同,且 \(s_{i} \ne s_{j}\) ,不存在一组 \(l,r(1 \le l<r \le |s_{i}|=|s_{j}|)\) 使得 \(\begin{cases} s_{i,1 \sim l-1}=s_{j,1 \sim l-1} \\ s_{i,l \sim r} \ne s_{j,l \sim r} \\ \exists k \in \{ i,j \},s_{k,l} \le s_{k,l+1} \le s_{k,l+2} \le \dots \le s_{k,r} \\ s_{i, r+1 \sim |s_{i}|}=s_{j,r+1 \sim |s_{j}|} \end{cases}\) 时,分别对 \(s_{i},s_{j}\) 整体排序即可,故 \(f(s_{i},s_{j})=2\) 。
-
\(f(s_{i},s_{j})=1337\) 的情况容易判断,而 \(f(s_{i},s_{j})=2\) 可以通过计算 \(f(s_{i},s_{j})=1\) 的情况相减得到。现在考虑计算 \(f(s_{i},s_{j})=1\) 的情况。
-
对于同一字符集的所有字符串先按字典序排序,设其分别为 \(s_{a_{1}} \sim s_{|a|}\) 。对于一段前缀 \(1 \sim l-1\) 相同的若干子串, \(l \sim r\) 有序的一定排在前面。
-
考虑固定 \(a_{i}\) 的某个极长有序区间 \(l \sim r\) (左右均不能扩展),然后统计去掉这个区间后相等的 \(s_{a_{j}}(i<j \le |a|)\) 的个数。
-
前缀相同的可以通过 \(LCP\) 处理。容易发现 \(LCP(s_{a_{i}},s_{a_{i+1}}) \dots LCP(s_{a_{i}},s_{a_{i+1}},s_{a_{i+2}}, \dots ,s_{|a|})\) 是单调不降的,且 \(LCP(s_{a_{i}},s_{a_{i+1}}, \dots ,s_{a_{j}})=\min\limits_{k=i+1}^{j} \{ LCP(s_{a_{k-1}},s_{a_{k}}) \}\) ,可以使用单调栈维护。
-
后缀则处理每个字符串的极长有序区间,然后将反串插到一棵 \(Trie\) 树里面,并记录位置和编号,然后二分处理即可。
- \(LCP\) 可以暴力求,也可以拼起来用 \(SA\) 加 \(ST\) 表维护。
-
假设当前定义了
vector<temlate>example
,则example.clear()
的真正作用是将example.size()
变成 \(0\) ,而不是清空内部元素,需要使用vector<temlate>().swap(example)
。点击查看代码
ll trie[200010][30],lcp[200010],tot=0,s_top=0; pair<ll,ll>st[200010]; vector<ll>id[200010],duan[200010]; string s,t; map<ll,ll>pos[200010]; map<string,vector<string> >f; map<string,vector<string> >::iterator it; ll val(char x) { return x-'a'+1; } void insert(string s,ll idx,ll len) { ll x=0,i; pos[idx][len]=x; id[x].push_back(idx); for(i=len-1;i>=0;i--) { if(trie[x][val(s[i])]==0) { tot++; trie[x][val(s[i])]=tot; } x=trie[x][val(s[i])]; pos[idx][i]=x; id[x].push_back(idx); } } ll ask(ll x,ll l,ll r) { return lower_bound(id[x].begin(),id[x].end(),r)-lower_bound(id[x].begin(),id[x].end(),l); } ll work(vector<string> a,ll len) { ll ans=2*a.size()*(a.size()-1)/2,i,j; for(i=0;i<=tot;i++) { for(j='a';j<='z';j++) { trie[i][val(j)]=0; } vector<ll>().swap(id[i]); } tot=0; sort(a.begin(),a.end()); for(i=0;i<a.size();i++) { pos[i].clear(); insert(a[i],i,len); } for(i=1;i<a.size();i++) { for(lcp[i]=0;lcp[i]<len-1;lcp[i]++) { if(a[i][lcp[i]]!=a[i-1][lcp[i]]) { break; } } } for(i=0;i<a.size();i++) { vector<ll>().swap(duan[i]); for(j=1;j<len;j++) { if(a[i][j-1]>a[i][j]) { duan[i].push_back(j); } } duan[i].push_back(len); } s_top=1; st[s_top]=make_pair(a.size(),-1); for(i=a.size()-1;i>=0;i--) { for(j=2;j<=s_top;j++) { ans-=1*ask(pos[i][*upper_bound(duan[i].begin(),duan[i].end(),st[j].second)],st[j].first,st[j-1].first); } while(s_top>=1&&st[s_top].second>=lcp[i]) { s_top--; } s_top++; st[s_top]=make_pair(i,lcp[i]); } return ans; } int main() { ll n,len,ans=0,sum=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>s; t=s; len=s.size(); sort(t.begin(),t.end()); f[t].push_back(s); } for(it=f.begin();it!=f.end();it++) { ans+=1337*(it->second.size())*sum+work(it->second,len); sum+=it->second.size(); } cout<<ans<<endl; return 0; }
\(G\) CF1400F x-prime Substrings
-
打表得到 \(x=1 \sim 20\) 的 \(x-prime\) 数量,观察到其数量较少。考虑对得到的 \(x-prime\) 建立 \(AC\) 自动机。
点击查看数量
sum[1]=1 sum[2]=1 sum[3]=1 sum[4]=1 sum[5]=3 sum[6]=1 sum[7]=8 sum[8]=3 sum[9]=8 sum[10]=7 sum[11]=54 sum[12]=2 sum[13]=139 sum[14]=23 sum[15]=40 sum[16]=15 sum[17]=928 sum[18]=6 sum[19]=2399 sum[20]=29
-
设 \(f_{i,j}\) 表示前 \(i\) 个字符,当前运行到 \(AC\) 自动机的状态 \(j\) ,且没有 \(x-prime\) 时最少需要删除的字符个数,状态转移方程为 \(f_{i,j}=\min(f_{i-1,j}+1,\min\limits_{(k,j) \in E} \{ f_{i-1,k} \})\) ,其中 \(j\) 不是 \(Trie\) 树上的叶子节点。
-
最终,有 \(\min\limits \{ f_{|s|,i} \}\) 即为所求。
点击查看代码
int t[30],trie[50000][15],fail[50000],vis[50000],f[2][50000],tot=0; char s[1010]; int val(char x) { return x-'0'; } void insert(int s[],int len) { int x=0,i; for(i=1;i<=len;i++) { if(trie[x][s[i]]==0) { tot++; trie[x][s[i]]=tot; } x=trie[x][s[i]]; } vis[x]=1; } void build() { int x,i; queue<int>q; for(i=1;i<=9;i++) { if(trie[0][i]!=0) { fail[trie[0][i]]=0; q.push(trie[0][i]); } } while(q.empty()==0) { x=q.front(); q.pop(); for(i=1;i<=9;i++) { if(trie[x][i]==0) { trie[x][i]=trie[fail[x]][i]; } else { fail[trie[x][i]]=trie[fail[x]][i]; q.push(trie[x][i]); } } } } bool check(int len,int x) { int sum,l,r; for(l=1;l<=len;l++) { sum=0; for(r=l;r<=len;r++) { sum+=t[r]; if(sum!=x&&x%sum==0) { return false; } } } return true; } void dfs(int len,int sum,int x) { if(sum==0) { if(check(len,x)==true) { insert(t,len); } } else { for(int i=1;i<=min(sum,9);i++) { t[len+1]=i; dfs(len+1,sum-i,x); } } } int main() { int x,n,ans=0x7f7f7f7f,i,j; cin>>(s+1)>>x; n=strlen(s+1); dfs(0,x,x); build(); for(i=1;i<=n;i++) { memset(f[i&1],0x3f,sizeof(f[i&1])); for(j=0;j<=tot;j++) { f[i&1][j]=min(f[i&1][j],f[(i-1)&1][j]+1); if(vis[trie[j][val(s[i])]]==0) { f[i&1][trie[j][val(s[i])]]=min(f[i&1][trie[j][val(s[i])]],f[(i-1)&1][j]); } } } for(i=0;i<=tot;i++) { ans=min(ans,f[n&1][i]); } cout<<ans<<endl; return 0; }
\(H\) CF955D Scissors
-
哈希或 \(KMP\) 预处理出 \(t_{1 \sim i}\) 在 \(s\) 中出现的最早位置 \(l_{i}\) 使得 \(t_{1 \sim i}=s_{l_{i}-i+1 \sim l_{i}}\) , \(t_{m-i+1 \sim m}\) 在 \(s\) 中出现的最晚位置 \(r_{m-i+1}\) 使得 \(t_{m-i+1 \sim m}=s_{r_{m-i+1} \sim r_{m-i+1}+i-1}\) 。
-
对拼不起来的情况进行大力分讨。
点击查看代码
const ll mod=1000000007,base=13331; ll jc[500010],a[500010],b[500010],l[500010],r[500010]; char s[500010],t[500010]; void sx_hash(char s[],ll a[],ll len) { for(ll i=0;i<=len;i++) { a[i]=(i==0)?0:(a[i-1]*base%mod+s[i])%mod; } } ll ask_hash(ll a[],ll l,ll r) { return (a[r]-a[l-1]*jc[r-l+1]%mod+mod)%mod; } int main() { ll n,m,k,flag=0,i,j; cin>>n>>m>>k>>(s+1)>>(t+1); for(i=0;i<=n;i++) { jc[i]=(i==0)?1:jc[i-1]*base%mod; } sx_hash(s,a,n); sx_hash(t,b,m); j=k; for(i=1;i<=min(m,k);i++) { while(j<=n&&ask_hash(b,1,i)!=ask_hash(a,j-i+1,j)) { j++; } if(ask_hash(b,1,i)==ask_hash(a,k-i+1,k)) { j=k; } l[i]=j; } j=n-k+1; for(i=1;i<=min(m,k);i++) { while(j>=1&&ask_hash(b,m-i+1,m)!=ask_hash(a,j,j+i-1)) { j--; } if(ask_hash(b,m-i+1,m)==ask_hash(a,n-k+1,n-k+i)) { j=n-k+1; } r[m-i+1]=j; } for(i=1;i<=m;i++) { if(l[i]<r[i+1]&&1<=l[i]-k+1&&l[i]<=n&&1<=r[i+1]&&r[i+1]+k-1<=n) { flag=1; cout<<"Yes"<<endl; cout<<l[i]-k+1<<" "<<r[i+1]<<endl; break; } } if(flag==0)//如果拼不起来 { for(i=1;i+m-1<=n;i++) { if(ask_hash(b,1,m)==ask_hash(a,i,i+m-1)) { flag=1; cout<<"Yes"<<endl; if(i+m-1>=k) { if(i+m+k-1<=n)//强制分给左面 { cout<<i+m-1-k+1<<" "<<i+m<<endl; } else//强制分给右面 { if(i<=n-k+1) { cout<<1<<" "<<i<<endl; } else { cout<<1<<" "<<n-k+1<<endl; } } } else//强制分给左面 { cout<<1<<" "<<k+1<<endl; } break; } } if(flag==0) { cout<<"No"<<endl; } } return 0; }
\(I\) CF1530E Minimax
-
What is 前缀函数 ?
-
设 \(cnt_{i}\) 表示 \(i\) 在 \(s\) 中出现的次数。
-
观察到若尽量使 \(f(t)\) 尽可能小,要尽可能将其中一部分字符串用其他字符隔开。
-
首先将 \(s\) 按字典序升序排序,然后大力分讨。
- 当 \(s_{1}=s_{|s|}\) 时,有 \(t=s\) 即为所求,此时 \(f(t)=\max\limits_{i=1}^{|t|} \{ nxt_{i} \}=nxt_{n}=|t|-1\) 。
- 当 \(s_{1} \ne s_{|s|},\exists i \in [1,|s|],cnt_{s_{i}}=1\) 时,先放一个 \(s_{i}\) ,然后按字典序输出即可。此时 \(f(t)=0\) 。
- 当 \(s_{1} \ne s_{|s|},0 \le cnt_{s_{1}}-2 \le |s|-cnt_{s_{1}},\forall i \in [1,|s|],cnt_{s_{i}}>1\) 时,考虑构造 \(f(t)=1\) 。又因要保证字典序最小,故 \(Border\) 一定由 \(s_{1}\) 产生。先放两个 \(s_{1}\) ,然后分别放置一个字典序次小的(若放完了,则放次次小的)和一个 \(s_{1}\) ,直到 \(s_{1}\) 放完,然后按字典序输出即可。
- 当 \(s_{1} \ne s_{|s|},cnt_{s_{1}}-2>n-cnt_{s_{1}},\forall i \in [1,|s|],cnt_{s_{i}}>1\) 且只包含两种字符时,考虑构造 \(f(t)=1\) 。 \(Border\) 的来源同理。先放一个 \(s_{1}\) ,再把字典序次小的放完,再把 \(s_{1}\) 放完即可。
- 当 \(s_{1} \ne s_{|s|},cnt_{s_{1}}-2>n-cnt_{s_{1}},\forall i \in [1,|s|],cnt_{s_{i}}>1\) 且包含三种或三种以上字符时,考虑构造 \(f(t)=1\) 。 \(Border\) 的来源同理。先放一个 \(s_{1}\) 和一个字典序最小的,再把 \(s_{1}\) 放完,再放一个字典序次次小的,然后按字典序输出即可。
点击查看代码
int cnt[30]; char s[100010]; int val(char x) { return x-'a'+1; } int main() { int t,n,id,id1,id2,i,j,k; cin>>t; for(k=1;k<=t;k++) { cin>>(s+1); n=strlen(s+1); id=id1=id2=0; sort(s+1,s+1+n); if(s[1]==s[n]) { cout<<(s+1)<<endl; } else { memset(cnt,0,sizeof(cnt)); for(i=1;i<=n;i++) { cnt[val(s[i])]++; } for(i='a';i<='z';i++) { if(cnt[val(i)]==1) { id=i; break; } } if(id!=0) { cout<<(char)id; cnt[val(id)]--; for(i='a';i<='z';i++) { for(j=1;j<=cnt[val(i)];j++) { cout<<(char)i; } } cout<<endl; } else { if(n-cnt[val(s[1])]>=cnt[val(s[1])]-2) { cout<<s[1]<<s[1]; cnt[val(s[1])]-=2; while(cnt[val(s[1])]>=1) { for(i=s[1]+1;i<='z';i++) { if(cnt[val(i)]>=1) { cout<<(char)i<<s[1]; cnt[val(i)]--; cnt[val(s[1])]--; break; } } } for(i=s[1]+1;i<='z';i++) { for(j=1;j<=cnt[val(i)];j++) { cout<<(char)i; } } cout<<endl; } else { for(i=s[1]+1;i<='z';i++) { if(cnt[val(i)]>=1) { id1=i; break; } } if(cnt[val(s[1])]+cnt[val(id1)]==n) { cout<<s[1]; cnt[val(s[1])]--; for(i=1;i<=cnt[val(id1)];i++) { cout<<(char)id1; } for(i=1;i<=cnt[val(s[1])];i++) { cout<<s[1]; } cout<<endl; } else { for(i=id1+1;i<='z';i++) { if(cnt[val(i)]>=1) { id2=i; break; } } cout<<s[1]<<(char)id1; cnt[val(s[1])]--; cnt[val(id1)]--; for(i=1;i<=cnt[val(s[1])];i++) { cout<<s[1]; } cout<<(char)id2; cnt[val(id2)]--; for(i=s[1]+1;i<='z';i++) { for(j=1;j<=cnt[val(i)];j++) { cout<<(char)i; } } cout<<endl; } } } } } return 0; }
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18083557,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。