Since12-19(71)
------------------------------------进入字符串的世界^O^ ----------------------------------------------
2016-12-19 周一 (22)
▲19:28:46 BZOJ3670 动物园(NOI2014): KMP/变式/YY
Y了一个下午没想到在BZOJ上水过去了^O^ ,最关键的一点->把不能重叠转化为能重叠的问题->对于i,只要找到最长的不重叠的子串h[i],既是前缀又是后缀,那么问题就转化成了求S[1...h[i]]内的能重叠的个数.直接可以递推得到.
求h[i]还是用KMP 的方法,对于一个i,一直找j,若当前的j不能会发生重叠,再用j=pre[j]找到第一个不会重叠的位置借口.
for(j=0,i=2;i<=n;i++){ while(j>0&&s[i]!=s[j+1])j=pre[j]; if(s[i]==s[j+1])j++; while(j>i/2)j=pre[j]; res=1ll*res*(f[j]+1)%P; }
▲20:30:41 HDU1711 Number Sequence KMP模板题
▲20:38:31 HDU2087 剪花布条 KMP模板题
▲20:43:24 HDU2203 亲和串 循环移位判断包含->把串复制,但是注意首先判断两个串长度!!!
▲21:42:04 HDU4513 吉哥系列故事——完美队形II 贪心/Manacher
2016-12-20 周二 (25)
▲11:20:29 HDU4749 Parade Show KMP/YY? 两个串匹配,当且仅当串的数字的相对大小是一样的->对于每个下标i,在两个串中,比a[i]大的a[j]个数等与比b[i]大的b[j]个数,比a[i]小的a[j]个数等与比b[i]小的b[j]个数.为此,由于a,b的范围很小,可以直接预处理出每个权值的出现次数的前缀和就可以了.我直接维护每个数字a[i]前面比它大和比它小的极值确定对应的b[i]的范围,再KMP就可以了,这样是不受a,b范围限制的!!
for(i=1;i<=m;i++){ if(!f[a[i]]){ f[a[i]]=i,d[i]=0; it=st.upper_bound(a[i]); if(it!=st.end())R[a[i]]=*it; else R[a[i]]=K+1;//没有比自己大的 if(it!=st.begin()){it--;L[a[i]]=*it;} else L[a[i]]=0; st.insert(a[i]); } else d[i]=1;//d[i]=1说明i不是第一次出现 } f[0]=0; f[K+1]=K+1; for(i=2,j=0;i<=m;i++){ while(j&&a[i]!=a[j+1])j=pre[j]; if(a[i]==a[j+1])j++; pre[i]=j; } for(i=1,j=0,x=0;i<=n;i++){ while(j&&!(x=chk(j+1,i)))j=pre[j]; if(chk(j+1,i)){ j++; f[a[j]]=b[i]; if(j==m)j=0,res++; } }
▲16:10:27 HDU4333 Revolving Digits 扩展KMP/循环节判断
1.扩展KMP用来求a串的每个后缀与b串的最长公共前缀.
2.注意题目中求的是不同的数字个数,因此要去重.此题中去重就是要找到循环节,可以通过KMP,求出nxt[len],假如串存在循环节,那么循环节长度就是len-nxt[len]的倍数.
▲16:40:04 HDU3336 Count the String KMP/递推结论:dp[i]=dp[pre[i]]+1,dp[i]表示前缀i中包含的可重叠的前缀个数.
2016-12-21 周三 (27)
▲15:08:07 BZOJ3110【好题加亮】 K大数查询 (ZJOI2013) 整体二分+线段树//树套树:
整体二分:解决答案在[L,R]的询问,通过求询问的答案和mid=(L+R)/2之间的关系再继续递归下去缩小LR范围.验证mid与答案的关系,就只要求出>mid的数的个数,而且是区间操作->线段树!!这样不用考虑插入的数的具体大小,只要考虑与mid的关系即可.
线段树套线段树:外层线段树维护权值(和主席树一样),每个节点又维护一个线段树维护下标区间,实现区间更新/区间求和.实现时,只要给内层线段树分配节点编号,并且记录左右儿子的编号,并且仅当更新到某个节点时,再给他编号,没有编号的点的cnt值就默认为0.在树套树里,不确定内存要开多大,就在不超过内存限制的前提下,尽可能开大.
▲21:39:43 BZOJ 蚂蚁寻路(ZJOI2013) DP/前缀和优化:最关键的一点就是把最后围成图形看成是长短长短长短...长的相邻柱形,再把变量分离量化,再用前缀和最值/求和搞一下就好了.边界情况要注意,可以直接求出来.
2016-12-22 周四(祝自己生日快乐啦啦啦~我17啦!) (28)
与单纯形共度的一天...
▲21:14:09 UOJ179 线性规划 单纯形模板
2016-12-23 周五 (35)
▲10:02:40 BZOJ3112 防守战线(ZJOI2013) 单纯形/对偶性优化
▲10:59:27 BZOJ1061 志愿者招募(NOI2008) 单纯形/对偶性优化 与上题代码一模一样...
通过对偶性可以省去初始化环节,原理: (D),(L)互为对偶.
#include<cstdio> #include<algorithm> #include<ctime> using namespace std; const int M=50; #define db double const db eps=1e-9,oo=1e30; db a[M][M]; int n,m,id[M],t[M],tp; void Pivot(int x,int y){//交换x,y...x是非基,y是基 int i,j,p=t[x],q=t[y],b=y-n; db k=-a[b][x]; swap(t[x],t[y]); swap(id[p],id[q]); for(i=0;i<=n;i++)a[b][i]/=k; a[b][x]=-1.0/k; for(j=0;j<=m;j++){ if(j==b)continue; k=a[j][x];a[j][x]=0; if(k<eps&&k>-eps)continue; for(i=0;i<=n;i++)a[j][i]+=k*a[b][i]; } } bool init(){ int i,j,x,y; while(1){ for(y=0,j=1;j<=m;j++){ if(a[j][0]>-eps)continue; for(i=1,x=0;i<=n;i++) if(a[j][i]>eps){x=i;if(rand()%2)break;} if(!x)return false; Pivot(x,j+n); y=1; } if(!y)return true; } } int Simplex(){//单纯形 if(!init())return -1;//说明无解 int i,j,x,y; while(1){ for(i=1,x=0;i<=n;i++){ if(a[0][i]>eps){x=i;if(rand()%2)break;} } if(!x)return 1; db mn=oo; for(y=0,j=1;j<=m;j++){ if(a[j][x]<-eps){ db d=-a[j][0]/a[j][x]; if(!y||d<mn)y=j,mn=d; } } if(!y)return 0; Pivot(x,y+n); } }
/* 第一步init 把负常数项转化为正的 通过把那一个约束条件的负系数非基变量与基变量交换 ,直到所有常数项都为正 若无法实现说明不可行... 然后得到了一组基础解 非基变量全部赋值为0,再得到基变量 找到目标值里一个系数为正的项k 假如没有说明已经是最优,就可以结束了.... 再求出它最紧的约束: a[j][0]-a[j][k]*x[k]=0 假如a[j][k]>0 ->x[k]=Min{a[j][0]/a[j][k]},记录对应的 y=j 假如 x[k]没有限制-> Unbounded 找到了x和 y 把x,y转换过来.. 转换(x,y): 1.转换t/id 2.找到第y个约束-> 每一项的系数 -> /a[y][x] 第x项变为1/a[y][x] 3.找到其他每个约束: 每第i项的系数都要加上 -a[j][x]*a[y][i] 对于目标值 第i项的系数 加上a[0][x]*a[y][i] */
▲11:26:56 HDU1358 Period KMP/判断循环节
▲15:36:41 HDU3613 Best Reward 扩展KMP/枚举//Hash
▲19:27:32 HDU4125 Moles 二叉树/线段树/KMP
注意set的内存不止4倍,而且速度非常慢,请谨慎使用.
▲21:58:28 HDU2222 Keywords Search AC自动机模板题
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> using namespace std; const int M=5e5+5; int Q[M],pre[M][26],cnt[M],fail[M],n,m,tot=0;//1300w char s[M*2],str[55];// void Trie(){ int i,k,cur=0,len=strlen(str); for(i=0;i<len;i++){ k=str[i]-'a'; if(!pre[cur][k])pre[cur][k]=++tot; cur=pre[cur][k]; } cnt[cur]++; } void AC_auto(){ int l=0,r=0,cur,i,nxt; for(i=0;i<26;i++)if(pre[0][i])Q[r++]=pre[0][i]; while(l<r){ cur=Q[l++]; for(i=0;i<26;i++){ if(pre[cur][i]){ nxt=Q[r++]=pre[cur][i]; fail[nxt]=pre[fail[cur]][i]; } else pre[cur][i]=pre[fail[cur]][i]; } } } void solve(){ int i,j,k,cur,ans=0; tot=0; scanf("%d",&m); while(m--){ scanf("%s",str); Trie(); } AC_auto(); scanf("%s",s+1); n=strlen(s+1); for(i=1,cur=0;i<=n;i++){ k=s[i]-'a'; while(pre[cur][k]==0&&cur!=0)cur=fail[cur]; cur=pre[cur][k]; k=cur; while(k&&~cnt[k]){ ans+=cnt[k]; cnt[k]=-1; k=fail[k]; } } printf("%d\n",ans); for(i=0;i<=tot;i++){ for(j=0;j<26;j++)pre[i][j]=0; fail[i]=cnt[i]=0; } } int main(){ int cas; scanf("%d",&cas); while(cas--)solve(); return 0; }
▲22:24:11 HDU2896 病毒入侵 AC自动机模板/注意内存能开多大开多大.但不要爆掉..
2016-12-24 周六 (39)
▲08:41:15 HDU3065 病毒持续入侵 AC自动机
小trick:①当输出具体包含的模板串编号,用正向表储存,补药用vector!!!!②多case注意清空数组!!(cnt/pre/head/fail)
▲09:28:06 HDU3407 String-Matching Automata AC自动机/KMP 一道英语能力比算法更重要的题目O__O "...
▲10:16:42 HDU3695 Computer Virus on Planet Pandora AC自动机
▲14:49:27 HDU2457/POJ3691 DNA repair KMP/AC自动机+DP 把与m个禁止串的最长前缀作为状态,最多有m*L个状态.再确定每个状态加一个字母后转为的新状态.
2016-12-27 周二(老爸生日快乐❤)(42)
▲13:50:14 POJ2778 DNA sequence AC自动机/矩阵快速幂
首先考虑这个问题:给定一个有向图,求从a点到b点走k步(可以重复经过某个点/边)的方案数.首先当k=1时,有一个初始的邻接矩阵A;假如k=2,C=∑A(a,i)*A(i,b);k=3,C=C*A...以此类推得到最后的矩阵 C=A^k.可以用快速幂进行优化.
▲17:01:29 HDU2243 考研路茫茫——单词情结 AC自动机/矩阵快速幂,求S=A+A2+A3+A4+....+Ak. 构造一个大矩阵.
▲22:29:57 hihoCoder 1455 【好题加亮】Rikka with Tree III 终态枚举/bitset/莫队
直接枚举等差中项x,其中有两个情况:
1)其中一个点在x的子树,只要维护x的子树即可
2)剩下两个点都在x的子树里,这种情况直接做就不方便了.稍微转换一下思路:不论是1,2哪种情况,都只有一个点在x的某个儿子里,那么直接枚举一定在x的子树里的那个点所在的子树即可.那么每次维护一个子树t[L,R],相当于为t的fa寻找答案.
维护[L,R]时采用莫队算法,先按左端点所在块排序,再按右端点排序
2016-12-28 周三 (43)
▲13:56:25 BZOJ 2343 【好题加亮】阿狸的打字机(NOI2011) AC自动机/DFS序/离线/BIT
首先在trie树上构出所有串.有一个结论:若x字符串是以y代表的串的后缀,那么y经过若干次fail一定可以到x.
那么问题就转化为了判断y到rt的每个节点是否可以经过若干次fail到x.因为每个节点的fail都是唯一确定的,所以可以把fail[c]作为fa[c]建立一棵树.那么节点t经过若干次fail到x,就可以转化为节点t是否在x的子树中.为了求在trie树上的点y到根的路径上的每个点对应的答案,可以将询问根据y排序,在bit中维护当前没有删除的字符,再通过dfs区间映射子树,把问题转为区间求和就ok了.
后缀数组我来了!!!
2016-12-29周四(44)
▲18:55:27 POJ2774 Long Long Message 后缀数组模板题 求最长公共子串
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 using namespace std; 6 const int M=2e5+5; 7 int sa[M],cnt[M],rnk[M],n,m,len,h[M],x[M],y[M],v[M]; 8 char s[M],str[M]; 9 int S=95; 10 void pt(int a[]){ 11 for(int i=1;i<=n;i++)printf("%d ",a[i]);puts(""); 12 } 13 bool cmp(int a,int b,int d){ 14 if(rnk[a]!=rnk[b])return true; 15 int x,y; 16 x=(a+d>n)?0:rnk[a+d]; 17 y=(b+d>n)?0:rnk[b+d]; 18 return x!=y; 19 } 20 int ty(int x){ 21 if(x<len)return 1; 22 if(x>len)return 2; 23 return 0; 24 } 25 void solve(){ 26 int i,j,k,a,b,c,ans=0,t; 27 len=strlen(s+1); 28 m=strlen(str+1); 29 s[++len]='`'; 30 for(i=1;i<=m;i++)s[i+len]=str[i]; 31 n=len+m; 32 for(i=1;i<=n;i++)cnt[v[i]=s[i]-S]++; 33 for(i=1;i<=27;i++)cnt[i]+=cnt[i-1]; 34 for(i=1;i<=n;i++)sa[cnt[v[i]]--]=i; 35 for(i=1;i<=n;i++)rnk[sa[i]]=rnk[sa[i-1]]+((s[sa[i]]==s[sa[i-1]])?0:1); 36 m=rnk[sa[n]]; 37 for(j=1;j<n&&m<n;j*=2){//枚举长度 38 for(t=0,i=n-j+1;i<=n;i++)x[++t]=i; 39 for(i=1;i<=n;i++){ 40 if(sa[i]>j)x[++t]=sa[i]-j; 41 } 42 for(i=0;i<=m;i++)cnt[i]=0; 43 for(i=1;i<=n;i++)cnt[rnk[i]]++; 44 for(i=1;i<=m;i++)cnt[i]+=cnt[i-1]; 45 for(i=n;i>=1;i--)sa[cnt[rnk[x[i]]]--]=x[i]; 46 y[sa[1]]=1; 47 for(i=2;i<=n;i++)y[sa[i]]=y[sa[i-1]]+(cmp(sa[i],sa[i-1],j)?1:0); 48 for(i=1;i<=n;i++)rnk[i]=y[i]; 49 m=rnk[sa[n]]; 50 } 51 for(i=1;i<=n;i++){ 52 if(rnk[i]==1){continue;} 53 k=h[rnk[i-1]]-1;// 54 b=sa[rnk[i]-1]; 55 c=min(n-i,n-b)+1; 56 for(j=max(1,k);j<=c&&s[i+j-1]==s[j+b-1];j++); 57 h[rnk[i]]=j-1; 58 } 59 for(i=2;i<=n;i++){ 60 if(ty(sa[i])!=ty(sa[i-1]))ans=max(ans,h[i]); 61 } 62 printf("%d\n",ans); 63 } 64 int main(){ 65 while(scanf("%s %s",s+1,str+1)!=EOF)solve(); 66 return 0; 67 }
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 using namespace std; 6 const int M=2e5+5; 7 int sa[M],cnt[M],rnk[M],n,m,len,v[M],h[M],x[M],y[M]; 8 char s[M],str[M]; 9 int S=95,d=0; 10 void pt(int a[]){ 11 for(int i=1;i<=n;i++)printf("%d ",a[i]);puts(""); 12 } 13 bool cmp(int a,int b){ 14 if(rnk[a]!=rnk[b])return true; 15 int x,y; 16 x=(a+d>n)?0:rnk[a+d]; 17 y=(b+d>n)?0:rnk[b+d]; 18 return x!=y; 19 } 20 bool cmp_sa(int a,int b){ 21 if(rnk[a]!=rnk[b])return rnk[a]<rnk[b]; 22 int x=(a+d)>n?0:rnk[a+d]; 23 int y=(b+d)>n?0:rnk[b+d]; 24 return x<y; 25 } 26 int ty(int x){ 27 if(x<len)return 1; 28 if(x>len)return 2; 29 return 0; 30 } 31 void solve(){ 32 int i,j,k,a,b,c,ans=0; 33 len=strlen(s+1); 34 m=strlen(str+1); 35 s[++len]='`'; 36 for(i=1;i<=m;i++)s[i+len]=str[i]; 37 n=len+m; 38 for(i=1;i<=n;i++)sa[i]=i,rnk[i]=s[i]; 39 sort(sa+1,sa+1+n,cmp_sa); 40 for(d=1;d<n&&m<n;d*=2){//枚举长度 41 sort(sa+1,sa+1+n,cmp_sa); 42 43 y[sa[1]]=1; 44 for(i=2;i<=n;i++){ 45 y[sa[i]]=y[sa[i-1]]+(cmp(sa[i],sa[i-1])?1:0); 46 } 47 for(i=1;i<=n;i++)rnk[i]=y[i]; 48 m=rnk[sa[n]]; 49 } 50 for(i=1;i<=n;i++){ 51 if(rnk[i]==1)continue; 52 k=h[rnk[i-1]]-1;// 53 b=sa[rnk[i]-1]; 54 c=min(n-i,n-b)+1; 55 for(j=max(1,k);j<=c&&s[i+j-1]==s[j+b-1];j++); 56 h[rnk[i]]=j-1; 57 } 58 for(i=2;i<=n;i++){ 59 if(ty(sa[i])!=ty(sa[i-1]))ans=max(ans,h[i]); 60 } 61 printf("%d\n",ans); 62 } 63 int main(){ 64 while(scanf("%s %s",s+1,str+1)!=EOF)solve(); 65 return 0; 66 }
2016-12-30周五(50)
▲09:55:10 HDU4691 Front Compression 后缀数组 求一个字符串两个后缀的最长公共前缀.
结论:LCP(sa[i],sa[j])=Min{h[k]} k∈[i+1,j],那么原问题就转为了求静态区间最值RMQ问题->ST算法/线段树等..
易错点:1)预处理Log2时,要注意优先级 ==比 &更高!!
2)不能把log2作为变量名,首字母要大写
3)倍增数组的j层在外面!!!!
▲14:07:22 POJ3294 Life Forms 后缀数组 二分+判定/单调队列尺取法 求出现次数超过K次不可重叠的最长子串.
▲16:15:11 POJ1743 Musical Theme 后缀数组+二分 求不可重叠最长重复子串 对于相对大小不变的情况,把差值作为权值->变成字符串匹配问题,注意首数字要和其他数字都不相同.题目要看清楚!!至少长度至少为5.
▲16:48:02 POJ3261 Milk Patterns 后缀数组+二分/单调队列 求出现次数至少为K次可重叠的最长子串
▲18:39:24 SPOJ694 Distinct Substring 求一个字符串所有不同的子串个数.后缀数组 把子串看作每个后缀的不同前缀,对于后缀i,只要去掉h[i]的部分就是答案了.
▲19:14:03 POJ2406 Power Strings 求字符串的循环节 KMP模板/后缀数组 求出每个后缀与原串的LCP,O(n)扫一遍即可. 但是后缀数组的倍增算法还是被卡T了 (T_T)
2016-12-31周六(51)
▲11:35:31 POJ3693 Maximum repetition substring 求出一个字符串中循环次数最多的子串,并且求出字典序最小的.后缀数组/RMQ ST算法.枚举循环节的长度,再枚举一定会经过的位置i,i+L,分别向后和向前匹配.在保证循环次数最多的情况下,再ST算法求出最小字典序.这里有个神奇的bug:求height数组时,要判断匹配的下标<=n!!!!!!调试毁人生!!!字典序最小真的太恶心了!!!
2017-01-01周日 Hello2017~(53)
▲10:24:55 BZOJ4539 树 (HHOI2016) 倍增+主席树:想到了正解,但是因为一个下标搞错了少了30分!!!【由于数据的神奇性????70%的数据n<=m!!】T_T
思路是把每个子树看作一个小点,再构建一棵树,然后倍增.用主席树来求大树某个节点对应的原树的节点.复杂度O(nlogn).
暴力做法:找到模板树,lower_bound来求出到大树上的对应编号,然后把子树dfs一遍,在大树上建边,询问求LCA.复杂度 m*n*logn+Qlog(m*n),能得30分.
▲15:34:45 BZOJ4538 网络(HNOI2016) 【整体二分】/dfs序!!! 每次在比赛时想到整体二分就会卡住...每次求答案>mid的区间.只要考虑权值>mid的操作能否更新到每个询问,一个询问被某一个更新到:在要求出现的时间段内,并且不在路径上.因为二分,确定了它们的值>mid,所以我们不用再考虑操作的具体权值,那么就可以按照时间排序,那么问题转化成,对于询问i,判断之前是否存在不经过i上,并且没有消失的路径.对于每条路径,更新除了路径的所有点是不方便的,那么就可以考虑更新路径,判断又可以转化为是否现在存在的每条路径都经过i,那么现在就需要一个数据结构来完成 路径更新,单点查询->DFS序+BIT,对一条路径的消失,相当于更新值-1即可.当然对于路径更新,也可以用树剖或者LCT,但是常数较大.
暴力做法:对于每个询问i,直接枚举所有存在的路径,判断否经过i点,直接暴力走向LCA.
2017-01-02周一(55)
▲11:12:08 BZOJ4537 最小公倍数(HNOI2016) 分块/并查集/启发式合并 问题转化为给出n点m边无向图,找一条路径(x,y)使得路径上边a,b的最大值分别为qa,qb.
先考虑暴力:对于每个询问(x,y,qa,qb),找到所有a<=qa,b<=qb的边,加入,最后看x,y是否联通,并且联通块中最大值是否是a,b.(若要满足条件,图中不可能有大于a或者b的边,那么只考虑<=qa,<=qb的边.首先要保证两点可以联通,也要保证路径最大值为a,b.由于可以是非简单路径,所以只要联通块能保证,那么一定存在路径满足要求.)复杂度:Q*(m+n),20分可得.
接下来考虑优化:现在有a,b两维,可以通过排序减少一维.对一个询问(qa,qb),只考虑出现在询问之前的所有边,但由于b的限制,我们还需要把所有边根据b再排序,然后再插入,这样和暴力似乎没啥区别..这种做法低效的原因之一就是对于qa,qb相近的询问,进行了很多次重复的操作,那就可以想是不是能够把相近的询问放在一起,但又有两个条件限制,可以想到莫队算法.先把左端点根据所属块排序,每个块内再根据右端点排序,我们把a,b看成左右端点.具体解决方案:处理第i个块的询问时,把前i-1个块的所有边取出,和i块所有询问一起根据b排序,对于一个询问k,在前i-1个块里合法的边都已经加上了,但在第I块里还有一些合法的边,我们也把它加入.由于加入的边可能对这个块里其他的询问是不合法的,所以要记录下加边之前的状态.在处理完k之后暴力撤回.
复杂度分析: 假设块的大小为S,处理每个块都要将边排序要mlogm,并查集不能路径压缩,但可以用启发式合并优化,总共m/S*(mlogm+nlogn),都要把每次询问都要暴力撤回,暴力添加块的内容,复杂度Q*S,总体复杂度m/S*(mlogm+nlogn)+Q*S,为了使复杂度尽可能小(视m,n,Q同阶).S≈sqrt(mlogm).
▲18:46:00BZOJ4556 字符串 (TJOI2016) 后缀数组/主席树/二分答案/RMQ.....谁知道暴力为什么这么快T_T...第一次rank1有点小激动!暴力的做法,二分,判定时直接从rk[c]往前扩展,直到MIn{h[k+1]..h[rk[c]]}<len,再扩展同时记录sa[k]是否在二分对应的区间中,往后扩展同理..这个暴力的做法居然碾压标程T T.只怪数据太水..
2017-01-03 周二(57)
▲11:37:15 POJ3415 Common Substrings 后缀数组/单调栈 题目并不难,注意单调性
bug1:答案LL
bug2:这是大bug!!!凡是要用到ST表预处理log数组,要for到n+3,所以要注意数组范围!!! 我一开始开了2e5+5,遇到两个长度为1e5的串,每个串后面加一个字符,然后再加3就刚好RE了 T_T..数组以后请开大点!!!
▲22:34:55 BZOJ4540 序列(HNOI2016) 分治/线段树/莫队/前缀和优化
2017-01-04周三 (58)
▲13:38:29BZOJ4542 大数(HNOI2016) 莫队算法/推性质
数字[l,r]可以表示为 ∑ai*10^(r-i),把10^r提出 10^r*∑ai*10^(-i)%P=0 假设P不是2或5,那么10^r显然不能被p整除,所以 ∑ai*10^(-i)%P=0.把每一项乘上10^n,得到
∑ai*10^(n-i) %P=0,i∈[L,R].假设si=∑ak*10^(n-k),k∈[1,i],那么可以通过s[r]-s[l-1]表示区间[l,r]的数值,那么只要s[r]=s[l-1],则数字模P余数为0.问题转化成求一个区间内,相同数字的对数->莫队算法.
▲20:16:23 UOJ70 新年的魔塔 提交答案/乱搞 注意提交答案题不要太拘泥于复杂度啊啊啊!!跑几个小时也没关系的!!
2017-01-05周四(61)
▲14:13:57 hihocoder 1445 后缀自动机2 求一个字符串不相同的子串个数/后缀自动机模板题
▲14:42:21 hihocoder 1449 后缀自动机3 求一个字符串长度为K的子串中出现最多的次数 K∈[1,len] 后缀自动机 利用了后缀自动机的树形性质
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 #define ll long long 6 using namespace std; 7 const int M=2e6+10; 8 const int S=26; 9 int head[M],ec=1,to[M],nxt[M],cnt[M],ans[M],fa[M],son[M][S],mx[M],mn[M],m,tot=0,now,rt; 10 char s[M];//所有的子串个数->∑mx-mn+1 11 void ins(int a,int b){ 12 to[ec]=b;nxt[ec]=head[a];head[a]=ec++; 13 } 14 int New(int len){mx[++tot]=len;return tot;} 15 void Add(int a){ 16 int x,y,v=now,z=New(mx[now]+1); 17 now=z; 18 cnt[z]=1; 19 while(v&&!son[v][a]){ 20 son[v][a]=z; 21 v=fa[v]; 22 } 23 if(!v){ 24 mn[z]=1,fa[z]=1; 25 return; 26 } 27 x=son[v][a]; 28 if(mx[x]==mx[v]+1){ 29 mn[z]=mx[x]+1,fa[z]=x; 30 return; 31 } 32 y=New(mx[v]+1); 33 for(int i=0;i<S;i++)son[y][i]=son[x][i]; 34 fa[y]=fa[x]; 35 fa[x]=fa[z]=y; 36 mn[x]=mn[z]=mx[y]+1; 37 mn[y]=mx[fa[y]]+1; 38 while(v&&son[v][a]==x)son[v][a]=y,v=fa[v]; 39 } 40 void dfs(int x){ 41 for(int i=head[x];i;i=nxt[i]){ 42 dfs(to[i]); 43 cnt[x]+=cnt[to[i]]; 44 } 45 ans[mx[x]]=max(ans[mx[x]],cnt[x]); 46 } 47 void solve(){ 48 int i,j,k,pre=0; 49 scanf("%s",s+1); 50 m=strlen(s+1); 51 rt=now=New(0); 52 for(i=1;i<=m;i++)Add(s[i]-'a'); 53 for(i=1;i<=tot;i++)ins(fa[i],i); 54 dfs(1); 55 for(i=m;i>=1;i--)ans[i]=max(ans[i],ans[i+1]); 56 for(i=1;i<=m;i++)printf("%d\n",ans[i]); 57 } 58 int main(){ 59 solve(); 60 return 0; 61 }
▲20:53:13 hihocoder 1457 后缀自动机3 给出n个数字串,求出n个串中所有不同的数字串的和(mod 1e9+7 )
对于一个状态x,找到所有的状态y,存在son[y][c]=x,也就是y添加一个字母c能得到x,那么sum[x]=sum[y]*10+c*size(y)
对于一个串的情况,直接用拓扑排序搞,对于多个串,可以用后缀数组的技巧,通过某个符号把每个串连起来,这里就有个问题,计算答案时不能考虑含":"的子串.所以在拓扑排序过程中记录下来就可以了.
2017-01-06周五(62)
▲11:27:53 POJ2774 LongLong message 最长公共子串儿 后缀自动机 /根据一个串建立自动机,另一个串在上面运行,实时维护当前的最长公共子串即可.对于求多个串的LCS,还是根据一个串建立自动机,剩下的串分别在上面运行,对每个状态维护能匹配到的长度的min值,最后对每个状态取max就是答案.
2017-01-07周六(64)
▲09:44:42 HDU5008 Boring String Problem 给定一个串,Q个询问,每个询问求字典序第Ki小的子串,多个串则选择下标最小者.强制在线.
后缀数组/每个串i,可以求出以sa[i]为起点的所有不同的串,并且是按照字典序排列的.那么只要二分就可以求出第K大.至于下标问题,先求出第一个第K小的串,然后向后找,找到所有包含这个串的后缀.可以暴力,也可以二分+RMQ.(暴力比二分+RMQ快真的不是我本意T_T)
▲14:26:18 UOJ 204 Boat 离散!!!
▲19:04:45 UOJ206 Gap 鸽巢原理
▲22:32:04 HDU4622 Reincarnation 后缀自动机+暴力 /hash+递推 hash做法:对于一个区间[l,r]若发现已经有区间[pos,pos+len-1]与s[l,l+len-1]相同,那么标记ans[l][l+len-1]++,ans[pos][l+len-1]--,保证对所有包含[pos,l+len-1]的区间答案只加一次.最后再递推出整个ans数组,表示[l,r]中所有不同的子串个数.回答O(1).
2017-01-08周日(67)
▲11:29:42 HDU5853 Jong Hyok and String 给定n个串,m个询问,每个询问给出一个串,问n个串中有多少个不同的子串和m在n个串里的出现位置的最后位置完全相同. 后缀自动机的裸题,直接构造出n个串的自动机,然后把每个询问的串跑一遍!!注意这里询问的是m串,而不是它的子串!!对于初始有多个串的情况,在每次一个串更新结束后,把now设为1即可!!(涨姿势!!)
▲14:51:58 hihocoder 1465 后缀自动机5 给定一个串s,和n个询问,求s中有多少个子串和a是循环同构的. 把a复制一下,然后在自动机上运行,预处理出每个状态在原串中出现的次数.
▲18:14:31 hihocoder 后缀自动机6 博弈/后缀自动机 给定两个串A,B.游戏这样规定,刚开始给出两个串的子串a,b,两个人可以在a或者b后面添加字符,使得添加后的a,b都是原串A,B的子串.若有人无法操作,则输.已知两人都足够聪明.现在求出能让先手获胜的所有方案的字典序第K小的方案.
1.博弈-Sg函数.对于必败态->f(x)=0.必胜态 f(x)=mex{f(y)},表示f(y)不存在的最小的非负整数.当前局面为必败态,当且仅当所有节点的sg函数异或和为0.现在局面有两个节点,状态为x,y,仅当sg[x]!=sg[y]时是必胜态.那么只要根据定义求出每个状态的SG函数即可.根据son数组直接可以确定每个状态的所有合法后继状态.
2.第K大:直接根据字典序dfs即可.
也我真棒!!
2017-01-09 周一(71)
▲10:50:03 BZOJ3676 回文串 后缀自动机/Manacher/倍增/或后缀数组 用manacher得到所有的回文串,然后问题就转化为求每个串出现的次数.Manacher匹配过程中中出现的所有子串就是原串所有本质不同的回文子串.求一个子串出现次数用倍增优化常数!!快快快!!
【这里还有一坑——回文自动机】
▲13:31:25 BZOJ3238 差异 (AHOI2013) 后缀数组+单调栈/后缀自动机 后缀数组做法与POJ3145类似.后缀自动机做法很神!!!把串反过来,问题就转化成求任意两个前缀的最长公共后缀和.对于两个表示前缀的状态,它们最长公共后缀的长度就是在fa树上LCA的长度!!只要dfs一遍地推出来就好了!神!!
▲14:51:05 BZOJ3629 诸神眷顾的幻想乡(ZJOI2015) 后缀自动机 题目的关键在于这棵树不超过20个叶子节点!!那么任意两点的路径一定是两个叶子节点的子路径.所以直接枚举每个叶子,以它为根,建立后缀自动机.最后所有叶子建立后的自动机上的每个节点的mx-mn+1和就是答案.
▲22:36:13UOJ131 品酒大会(NOI2015) 后缀自动机/后缀数组 给定一个串,求所有后缀LCP>=r的方案数和权值乘积最大值.后缀自动机做法:把串反过来,问题就变成在树上,直接递推dp一下. 后缀数组做法:并查集.从大到小枚举r,再维护并查集,每次考虑h为r的点,更新再合并.同时维护最值.