1.字符串专题
字符串专题
\(A\) CF1037H Security
-
考虑枚举答案串和 \(t\) 的 \(\operatorname{LCP}\) 然后问题的难点就来到了怎么判断一个串是否在 \(s_{l \sim r}\) 中出现过。
-
每次都在 \(\{ rk \}\) 上二分并对后缀和模式串进行暴力比较的时间复杂度无法接受,需要进一步优化。
-
观察到每次在末尾添加一个字符对应的 \(\{ rk \}\) 区间就会缩小,故可以考虑分别二分左右端点进行得到极长区间 \([L,R]\) 。
-
此时转化为查询 \(\{ rk \}\) 区间上 \([L,R]\) 对应的 \(sa\) 有没有在 \([l,r]\) 内的,主席树维护二维数点即可。
-
由于需要字典序最小,取最后一次得到的答案即可。
点击查看代码
char s[100010],t[200010]; struct SA { int sa[100010],rk[200010],oldrk[200010],id[100010],cnt[100010],key[100010]; int val(char x) { return (int)x; } void counting_sort(int n,int m) { memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) { cnt[key[i]]++; } for(int i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(int i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init(char s[],int len) { int m=127,tot=0,num=0; for(int i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(int w=1;tot!=len;w<<=1,m=tot) { num=0; for(int i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(int i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(int i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(int i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(int 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; } } } }S; struct PDS_SMT { int root[100010],rt_sum=0; struct SegmentTree { int ls,rs,sum; }tree[100010<<5]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) int build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].sum=0; return rt_sum; } void update(int pre,int &rt,int l,int r,int pos,int val) { rt=build_rt(); tree[rt]=tree[pre]; tree[rt].sum+=val; if(l==r) { return; } int mid=(l+r)/2; if(pos<=mid) { update(lson(pre),lson(rt),l,mid,pos,val); } else { update(rson(pre),rson(rt),mid+1,r,pos,val); } } int query(int rt1,int rt2,int l,int r,int x,int y) { if(x<=l&&r<=y) { return tree[rt2].sum-tree[rt1].sum; } int mid=(l+r)/2,ans=0; if(x<=mid) { ans+=query(lson(rt1),lson(rt2),l,mid,x,y); } if(y>mid) { ans+=query(rson(rt1),rson(rt2),mid+1,r,x,y); } return ans; } }T; int divide_left(int l,int r,int len,char x) { int mid,ans=0; while(l<=r) { mid=(l+r)/2; if(s[S.sa[mid]+len-1]>=x) { ans=(s[S.sa[mid]+len-1]==x)?mid:ans; r=mid-1; } else { l=mid+1; } } return ans; } int divide_right(int l,int r,int len,char x) { int mid,ans=0; while(l<=r) { mid=(l+r)/2; if(s[S.sa[mid]+len-1]==x) { ans=mid; l=mid+1; } else { r=mid-1; } } return ans; } pair<int,char> query(int l,int r,int len,int n) { t[len+1]=0; int st=1,ed=n,pos1,pos2,lcp=-1; char ans=0; for(int i=0;i<=min(len,r-l);i++) { if(i!=0) { st=divide_left(st,ed,i,t[i]); if(st==0) { break; } ed=divide_right(st,ed,i,t[i]); } for(int j=max((char)'a',(char)(t[i+1]+1));j<=(char)'z';j++) { pos1=divide_left(st,ed,i+1,j); if(pos1!=0) { pos2=divide_right(pos1,ed,i+1,j); if(T.query(T.root[pos1-1],T.root[pos2],1,n,l,r-i)>=1) { lcp=i; ans=j; break; } } } } return make_pair(lcp,ans); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,l,r,len,i,j; pair<int,char> tmp; cin>>(s+1)>>m; n=strlen(s+1); S.init(s,n); for(i=1;i<=n;i++) { T.update(T.root[i-1],T.root[i],1,n,S.sa[i],1); } for(i=1;i<=m;i++) { cin>>l>>r>>(t+1); len=strlen(t+1); tmp=query(l,r,len,n); if(tmp.first==-1) { cout<<-1<<endl; } else { for(j=1;j<=tmp.first;j++) { cout<<t[j]; } cout<<tmp.second<<endl; } } return 0; }
\(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
-
将 \(s\) 和 \(\{ t \}\) 顺次相连,中间用分隔符连接。
-
\(s_{p_{l} \sim p_{r}}\) 在某个后缀 \(i\) 中出现过 \(\operatorname{LCP}(i,p_{l}) \ge p_{r}-p_{l}+1\) 。将 \(\operatorname{LCP}\) 写成 \(rk\) 区间上对 \(\{ height \}\) 取 \(\min\) 的形式,发现和 luogu P2178 [NOI2015] 品酒大会 的样子很像。
-
考虑从大到小按照 \(height\) 合并,可以二分找到左右两部分的极长区间建笛卡尔树,也可以直接类似 \(Kruskal\) 重构树的思想建树,询问时先倍增跳到根节点然后查询子树内一段值域内的众数,线段树合并即可。
点击查看代码
int s[600010],st[600010],ed[600010],p[600010],c[600010],fa[1200010][25]; char t[600010],ss[600010]; vector<int>e[1200010]; struct SA { int sa[600010],rk[1200010],oldrk[1200010],id[600010],cnt[600010],key[600010],height[600010]; int val(int x) { return (int)x; } void counting_sort(int n,int m) { memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) { cnt[key[i]]++; } for(int i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(int i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init(int s[],int len) { int m=60000,tot=0,num=0; for(int i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(int w=1;tot!=len;w<<=1,m=tot) { num=0; for(int i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(int i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(int i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(int i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(int 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(int 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; } } }S; struct SMT { int root[1200010],rt_sum; struct node { int val,cnt; node operator + (const node &another) const { if(cnt>another.cnt) { return *this; } return (cnt==another.cnt)?((node){min(val,another.val),cnt}):another; } }; struct SegmentTree { int ls,rs; node info; }tree[1200010<<5]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) int build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=0; tree[rt_sum].info=(node){0,0}; return rt_sum; } void pushup(int rt) { tree[rt].info=tree[lson(rt)].info+tree[rson(rt)].info; } void update(int &rt,int l,int r,int pos) { rt=(rt==0)?build_rt():rt; if(l==r) { tree[rt].info=(node){pos,1}; return; } int mid=(l+r)/2; if(pos<=mid) { update(lson(rt),l,mid,pos); } else { update(rson(rt),mid+1,r,pos); } pushup(rt); } int merge(int rt1,int rt2,int l,int r) { if(rt1==0||rt2==0) { return rt1+rt2; } int rt=build_rt(); if(l==r) { tree[rt]=tree[rt1]; tree[rt].info.cnt+=tree[rt2].info.cnt; return rt; } int mid=(l+r)/2; lson(rt)=merge(lson(rt1),lson(rt2),l,mid); rson(rt)=merge(rson(rt1),rson(rt2),mid+1,r); pushup(rt); return rt; } node query(int rt,int l,int r,int x,int y) { if(rt==0) { return (node){x,0}; } if(x<=l&&r<=y) { return tree[rt].info; } int mid=(l+r)/2; if(y<=mid) { return query(lson(rt),l,mid,x,y); } if(x>mid) { return query(rson(rt),mid+1,r,x,y); } return query(lson(rt),l,mid,x,y)+query(rson(rt),mid+1,r,x,y); } pair<int,int>ask(int rt,int l,int r,int x,int y) { node tmp=query(rt,l,r,x,y); return make_pair(tmp.val,tmp.cnt); } }T; struct DSU { int fa[1200010]; void init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; } } int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); } }D; bool cmp(int a,int b) { return S.height[a]>S.height[b]; } void dfs(int x,int father) { fa[x][0]=father; for(int i=1;i<=20;i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; } for(int i=0;i<e[x].size();i++) { dfs(e[x][i],x); } } void kruskal(int n,int m) { D.init(2*n); for(int i=1;i<=n;i++) { p[i]=i; } sort(p+2,p+1+n,cmp); for(int i=2,tot=n;i<=n&&tot<=2*n-1;i++) { tot++; c[tot]=S.height[p[i]]; int x=D.find(p[i]),y=D.find(p[i]-1); D.fa[x]=D.fa[y]=tot; e[tot].push_back(x); e[tot].push_back(y); T.root[tot]=T.merge(T.root[x],T.root[y],1,m); } dfs(2*n-1,0); } int ask(int x,int k) { for(int i=20;i>=0;i--) { if(c[fa[x][i]]>=k) { x=fa[x][i]; } } return x; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,q,len,l,r,pl,pr,i,j; pair<int,int>tmp; cin>>(t+1)>>m; n=strlen(t+1); for(i=1;i<=n;i++) { s[i]=t[i]-'a'+1; } for(i=1;i<=m;i++) { cin>>(t+1); len=strlen(t+1); n++; s[n]=1000+i; st[i]=n+1; for(j=1;j<=len;j++) { n++; s[n]=t[j]-'a'+1; } ed[i]=n; } S.init(s,n); for(i=1;i<=m;i++) { for(j=st[i];j<=ed[i];j++) { T.update(T.root[S.rk[j]],1,m,i); } } kruskal(n,m); cin>>q; for(i=1;i<=q;i++) { cin>>l>>r>>pl>>pr; tmp=T.ask(T.root[ask(S.rk[pl],pr-pl+1)],1,m,l,r); cout<<tmp.first<<" "<<tmp.second<<endl; } return 0; }
\(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) 进行许可。