• [bzoj1692][Usaco2007 Dec]队列变换_后缀数组_贪心


    队列变换 bzoj-1692 Usaco-2007 Dec

    题目大意:给定一个长度为$n$的字符串。每次从头或尾取出一个字符加到另一个字符串里。要求变换后生成的字符串字典序最小,求字典序最小的字符串。

    注释:$1le nle 3cdot 10^4$。


    想法

    考虑暴力怎么做:

    每次把当前字符串左右较小的取出来。如果一样就对着往中间枚举,直到不一样为止。

    这样时间复杂度为$O(n^2)$,全是一样的字符即可卡掉。

    显然上述过程我们就是比较正反两个字符串谁的字典序更小对吧。

    那么我们把原串翻转然后接在原串后面。两个串中间加一个非字符集字符。

    对这个新串建立后缀数组然后弄在这个新串上弄两个指针。

    只需要比较这两个指针对应后缀谁在$sa$中更靠前即可,因为中间有一个不可见字符隔着所以互相不会影响。

    如果是前面的指针较大那就把前面的指针向后移动,反之把后面的指针向前移动。输出刚才指向的字符即可。

    由于后缀数组可以将后缀排序所以只要理解了上面的暴力那么正解的正确性显然。

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 60010 
    using namespace std;
    int Ws[N],wn[N],wa[N],wb[N],n,m=28,rk[N],ht[N],r[N<<1],sa[N],wv[N],x[N],y[N];
    inline char nc() {static char buf[100000],*p1,*p2;return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;}
    inline int rd() {int x=0; char c=nc(); while(c<'0'||c>'9') c=nc(); while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=nc(); return x;}
    inline int rc() {char c=nc();while(c<'A'||c>'Z')c=nc(); return c-'A'+1;}
    void build_sa()
    {
    	int i,j,p,*x=wa,*y=wb,*t;
    	for(i=0;i<m;i++) Ws[i]=0;
    	for(i=0;i<n;i++) Ws[x[i]=r[i]]++;
    	for(i=1;i<m;i++) Ws[i]+=Ws[i-1];
    	for(i=n-1;~i;i--) sa[--Ws[x[i]]]=i;
    	for(j=p=1;p<n;j<<=1,m=p)
    	{
    		for(p=0,i=n-j;i<n;i++) y[p++]=i;
    		for(i=0;i<n;i++) if(sa[i]-j>=0) y[p++]=sa[i]-j;
    		for(i=0;i<n;i++) wv[i]=x[y[i]];
    		for(i=0;i<m;i++) Ws[i]=0;
    		for(i=0;i<n;i++) Ws[wv[i]]++;
    		for(i=1;i<m;i++) Ws[i]+=Ws[i-1];
    		for(i=n-1;~i;i--) sa[--Ws[wv[i]]]=y[i];
    		for(t=x,x=y,y=t,i=p=1,x[sa[0]]=0;i<n;i++)
    		{
    			if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+j]==y[sa[i-1]+j]) x[sa[i]]=p-1;
    			else x[sa[i]]=p++;
    		}
    	}
    	for(i=1;i<n;i++) rk[sa[i]]=i;
    	for(i=p=0;i<n-1;ht[rk[i++]]=p)
    		for(p?p--:0,j=sa[rk[i]-1];r[i+p]==r[j+p];p++);
    }
    int main()
    {
    	n=rd();
    	int i,len=n,k;
    	for(i=0;i<n;i++) r[i]=rc();
    	r[n]=0;
    	for(i=n+1;i<=n+n;i++) r[i]=r[n+n-i];
    	n<<=1; r[n++]=0;
    	build_sa();
    	int j=len-1; i=0;
    	for(k=1;k<=len;k++)
    	{
    		if(rk[i]<rk[len+len-j]) printf("%c",r[i++]+'A'-1);
    		else printf("%c",r[j--]+'A'-1);
    		if(k%80==0) puts("");
    	}
    	return 0;
    }
    

    小结:后缀数组的本质就是把所有后缀排序。好好应用这个功能会事半功倍的啊~

  • 相关阅读:
    循环取出正则匹配的内容(遍历).
    遍历datatable的方法
    asp.net获取URL和IP地址
    匹配多个字符串方法
    世界杯决赛不好看,有点像假球,被裁判黑了?
    2010南非世界杯冠军预测:荷兰夺冠!
    查了几家人民广场附近的川菜和湘菜馆
    白平衡(转载自wikipedia)
    梦想与感动
    港澳旅游相关备忘
  • 原文地址:https://www.cnblogs.com/ShuraK/p/10108536.html
Copyright © 2020-2023  润新知