BZOJ 2882 浅谈最小表示法后缀数组求解+线性求解
2017-07-31 09:10
411 查看
世界真的很大
最小表示法这个东西很偏
一开始做这道题想到的是后缀数组的做法,nlogn的,后来借这个契机学了下最小表示法,重新写了一遍,O(n)的
想不通为什么n比nlogn快10倍。。
可能后缀数组的常数偏大吧
看题先:
description:
小敏和小燕是一对好朋友。 他们正在玩一种神奇的游戏,叫Minecraft。 他们现在要做一个由方块构成的长条工艺品。但是方块现在是乱的,而且由于机器的要求,他们只能做到把这个工艺品最左边的方块放到最右边。 他们想,在仅这一个操作下,最漂亮的工艺品能多漂亮。 两个工艺品美观的比较方法是,从头开始比较,如果第i个位置上方块不一样那么谁的瑕疵度小,那么谁就更漂亮,如果一样那么继续比较第i+1个方块。如果全都一样,那么这两个工艺品就一样漂亮。
input
第一行两个整数n,代表方块的数目。 第二行n个整数,每个整数按从左到右的顺序输出方块瑕疵度的值。
output
一行n个整数,代表最美观工艺品从左到右瑕疵度的值。
现在知道这东西就是一个裸的最小表示法
但第一次做的时候实在是不知道有这么个东西,思路是这样的
考虑重新排列之后字典序最小,想到后缀数组的后缀排名就是按后缀的字典序排的,但是这样有可能会忽略,若第一第二名的后缀,第一比第二短,其后一位接上第一位的时候,字典序反而会大于第二位
现在就需要解决这个东西了
考虑是让原序列从某个位置断开接到后面,可以想到是一个环,选择一个位置作为开始位置使得字典序最小
对于序列成环这种东西我们有经典解法,要记住,把原序列复制一遍接到后面,这样从原序列的每一个位置都是一个完整(多一点)的循环了
在这个构造上面求后缀排名第一的就行了,几乎没什么细节,后缀数组写熟了的话,也就10分钟吧
完整代码:
#include<stdio.h> #include<algorithm> using namespace std; int n,mxn=0; int wa[800010],wb[800010],wv[800010],ws[800010]; int rank[800010]; int sa[800010],r[800010]; bool cmp(int *r,int a,int b,int l) { return r[a]==r[b]&&r[a+l]==r[b+l]; } void da(int *r,int *sa,int n,int m) { int *x=wa,*y=wb,*t; for(int i=0;i<m;i++) ws[i]=0; for(int i=0;i<n;i++) ws[x[i]=r[i]]++; for(int i=1;i<m;i++) ws[i]+=ws[i-1]; for(int i=n-1;i>=0;i--) sa[--ws[x[i]]]=i; for(int p=1,j=1;p<n;j<<=1,m=p) { p=0; for(int i=n-j;i<n;i++) y[p++]=i; for(int i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; for(int i=0;i<n;i++) wv[i]=x[y[i]]; for(int i=0;i<m;i++) ws[i]=0; for(int i=0;i<n;i++) ws[wv[i]]++; for(int i=1;i<m;i++) ws[i]+=ws[i-1]; for(int i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i]; int i; for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++) x[sa[i]]= cmp(y,sa[i-1],sa[i],j) ? p-1 : p++; } } int main() { scanf("%d",&n); for(int i=0;i<n;i++) scanf("%d",&r[i]),mxn=max(mxn,r[i]); for(int i=n;i<2*n;i++) r[i]=r[i-n]; da(r,sa,2*n,mxn+1); int x=sa[1]%n,cnt=0; for(int i=x;i<n*2;i++) { cnt++; printf("%d",r[i]); if(cnt!=n) printf(" "); else break ; } return 0; } /* Whoso pulleth out this sword from this stone and anvil is duly born King of all England */
现在来谈谈最小表示法这个东西。虽然很偏,但是他的思想可能还是比较重要,而且,反正短,学起来也比较好懂,学呗
可以去看一下ZY大大的文章
文章里是判断两个字符串是不是同构,通过两个的最小表示法来判断
对于单独的一个字符串,也可以用同样的原理求其最小表示法
考虑两个指针i,j,从i,j向后匹配,设在i+k和j+k这个位置,字符串的值不同了,失配了,就是说i到i+k-1和j到j+k-1这两段是完全相同的,通过比较i+k和j+k的两个字符,就知道i到i+k和j到j+k这两段的字典序哪个小了
假设i+k的字典序较大,那么i到i+k都不可能是最小表示法的起始位置,因为至少存在j到j+k的同样位置使其字典序比他小
所以i可以直接跳到i+k+1的位置
考虑重复以上操作,如果i或者j已经是最小表示法的位置了,就绝不会再动了,因为找不到另一个位置的值比其小,而只有字典序较大的指针才会移动。
所以直到一个指针移动到末尾,那另一个指针就是最小表示法的起始位置了
大概就是这样,这个也需要把原序列复制后接到一起
细节上没什么要注意的,代码也很短
完整代码:
#include<stdio.h> #include<algorithm> using namespace std; int n; int a[800010]; int MEXP() { int i=1,j=2,k=0; while(i<=n&&j<=n&&k<=n) { int tmp=a[i+k]-a[j+k]; if(!tmp) k++; else { if(tmp>0) i+=k+1; else j+=k+1; if(i==j) i++; k=0; } } return min(i,j); } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i+n]=a[i]; int x=MEXP(),cnt=0; for(int i=x;i<=2*n;i++) { cnt++; printf("%d",a[i]); if(cnt!=n) printf(" "); else break ; } return 0; } /* Whoso pulleth out this sword from this stone and anvil is duly born King of all England */
嗯,就是这样
相关文章推荐
- [BZOJ]2882 工艺 最小表示法
- bzoj 2882(最小表示法)
- bzoj2882 工艺(后缀自动机(最小表示法))
- 【最小表示法】BZOJ2882-工艺
- BZOJ 2882: 工艺 最小表示法
- [最小循环表示 后缀自动机 模板题] BZOJ 2882 工艺
- [BZOJ2882]工艺(后缀自动机+stl||最小表示法)
- bzoj2882 工艺【最小表示法】
- BZOJ 2882 工艺 ——后缀自动机 最小表示法
- BZOJ 2882 工艺 字典序最小的循环同构串(最小表示法 详解)
- BZOJ.2882.工艺(后缀自动机 最小表示 map)
- 【bzoj2882】【工艺】【最小表示法】
- bzoj 1398 &&bzoj 2882最小表示法
- 【BZOJ 2882】工艺 最小表示法
- 【BZOJ2882】【字符串的最小表示】工艺
- BZOJ 2882 后缀数组/最小表示法 解题报告
- 【BZOJ2882】工艺【最小表示法】
- 【bzoj4484】【JSOI2015】【最小表示】【拓扑排序+bitset】
- BZOJ 2150 浅谈二分图Bipartite Graph及DAG最小路径覆盖
- 【BZOJ 1398】 1398: Vijos1382寻找主人 Necklace (最小表示法)