1. 程式人生 > >[SCOI2012]喵星球上的點名——堪稱九種方法做的題

[SCOI2012]喵星球上的點名——堪稱九種方法做的題

題意:

給你N個串對,M個詢問串,對每個詢問串求是多少串對的子串(在串對的某一箇中作為子串),以及每個串對最終是包含了多少詢問串

 

方法眾多。。

可謂字串家族八仙過海各顯神通。

複雜度不盡相同,O(nlogn),O(nsqrt(n)),O(玄學)(也就是暴力)

(資料比較水,所以一些暴力就過去了)

做法基本都是離線。

 

法一:AC自動機+暴力

對詢問串建AC自動機,把主串往上跑。

匹配到了一個節點,就暴力跳fail,把沿途的點如果是詢問串的結尾,ans++,並打上標記,防止重複計數。

一串1111111,隨便卡。

法二:AC自動機+hash

詢問串或者主串可能有相同的,,,hash一下,減少理論複雜度。。。

還是隨便卡。

法三:AC自動機+fail樹虛樹

這個是正解O(nlogn)。

但是不會虛樹,咕咕咕。

法四:字尾陣列+hash

AC自動機比較辣雞,字尾家族表示不服。

一個字尾的和詢問串的lcp是詢問串的長度的話,那麼這個詢問串就是子串。

考慮一個詢問會被哪些字尾包含,我們把所有的詢問串和子串用分隔符隔開,然後跑SA,HEIGHT

對於每個詢問串的開頭位置,往左往右二分出所有出現的位置。

這個區間[l,r]就是這個詢問串的所有出現位置~!

luogu題解第一篇dalao說,可以不用二分,線性處理出l,r的位置。

然後我寫了單調佇列,,,,然鵝顯然這個區間端點並沒有單調性。。。然後WA了半天。。。。

不知是這個dalao口胡錯了,還是我太菜了?

怎麼統計答案?

由於區間長度期(shu)望(ju)不(tai)大(shui),可以暴力掃一遍這個區間,然後輕鬆統計答案。

一串111111,應該還是能卡。

法五:字尾陣列+莫隊

莫隊教導我們:幹嘛要直接暴力?

處理出詢問區間之後,莫隊可以輕鬆統計第一問,

第二問的話:差分。每新加入一個顏色,就把這個顏色的答案加上剩餘詢問的次數,刪除這個顏色的時候,就把剩餘次數減掉。這樣,處理所有包含這個顏色的詢問的時候,這些詢問一定貢獻到了這個顏色裡。

O(nsqrt(n))

法六:字尾陣列+樹狀陣列

莫隊歸根到底,還是暴力啊。。。。

再看一看第一問是什麼:統計區間顏色的數量?哦,,[SDOI2009]HH的項鍊!!

樹狀陣列來也。

第二問呢?反過來,把詢問當做樹狀陣列中加入的點值。

到L的時候,bit(L)++,到R的時候,bit(L)--,到i的時候,ans+=query(i)-query(pre[i])

類似掃描線。

本質上還是對於每個顏色第一次被區間包含的時候,把這個區間的貢獻加上。為什麼這裡要把bit(L)--?為了消除區間兩端在中間的情況。

 

那為什麼不把bit(R)--?這樣前面的相同部分並不能減去

 

 反而加上了-1

我寫的就是這個方法:

注意:

1.還是二分吧。。

2.注意我們是在SA陣列上操作,pos記錄的是原串的所屬,所以,在SA上迴圈的時候,查詢這個字尾的所屬,用pos[sa[i]]

#include<bits/stdc++.h>
#define il inline
#define reg register int
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=4e5+5;
const int C=1e4+2;
int x[N],y[N],c[N],sa[N],hei[N],rk[N];
int n,m;
int s[N+233];
void SA(int n){
    int m=C+2;
    for(reg i=1;i<=n;++i) ++c[x[i]=s[i]];
    for(reg i=1;i<=m;++i) c[i]+=c[i-1];
    for(reg i=1;i<=n;++i) sa[c[x[i]]--]=i;
    for(reg k=1;k<=n;k<<=1){
        int num=0;
        for(reg i=n-k+1;i<=n;++i) y[++num]=i;
        for(reg i=1;i<=n;++i){
            if(sa[i]-k>=1) y[++num]=sa[i]-k;
        }
        for(reg i=1;i<=m;++i) c[i]=0;
        for(reg i=1;i<=n;++i) ++c[x[i]];
        for(reg i=1;i<=m;++i) c[i]+=c[i-1];
        for(reg i=n;i>=1;--i) sa[c[x[y[i]]]--]=y[i],y[i]=0;
        swap(x,y);
        num=1;
        x[sa[1]]=1;
        for(reg i=2;i<=n;++i){
            x[sa[i]]=((y[sa[i]]==y[sa[i-1]])&&(y[sa[i-1]+k]==y[sa[i]+k])?num:++num);
        }
        if(num==n) break;
        m=num;
    } 
}
void HEI(int n){
    for(reg i=1;i<=n;++i) rk[sa[i]]=i;
    int k=0;
    for(reg i=1;i<=n;++i){
        if(k)--k;
        if(rk[i]==1) continue;
        int j=sa[rk[i]-1];
        while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) ++k;
        hei[rk[i]]=k;
    }
}
int pos[N],pre[N],las[N],id[N];
int nxt[N];
struct question{
    int L,R,id,ans;
    bool friend operator <(question a,question b){
        return a.L<b.L;
    }
}que[N];
struct node{
    int L,p,c;
    bool friend operator <(node a,node b){
        if(a.p!=b.p) return a.p<b.p;
        return a.c>b.c;
    }
}po[2*N];
int cnt;
int chang[N];
int f[N][20];
int lg[N];
int tot;
int query(int x,int y){
    if(x==y) return tot-x+1;
    if(x>y) swap(x,y);
    ++x;
    int len=lg[y-x+1];
//    cout<<" rmq "<<len<<" "<<f[x][len]<<" "<<f[y-(1<<len)+1][len]<<endl;
    int ret=min(f[x][len],f[y-(1<<len)+1][len]);
    return ret;
}
void prewrk(int n){
///    cout<<" nn "<<n<<endl;
    for(reg i=1;i<=n;++i) {
        f[i][0]=hei[i];
        lg[i]=(i>>(lg[i-1]+1))?lg[i-1]+1:lg[i-1];
    }
    for(reg j=1;j<=18;++j){
        for(reg i=1;i+(1<<j)-1<=n;++i){
            f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
            //cout<<" i j "<<i<<" "<<j<<" : "<<f[i][j]<<endl;
        }
    }
    
    for(reg i=1;i<=n;++i){
        if(pos[sa[i]]==0&&id[sa[i]]>0){
            int tmp=i;
            int l=1,r=i-1;
            
            que[id[sa[i]]].id=id[sa[i]];
            while(l<=r){
                int mid=(l+r)>>1;
                if(query(mid,i)>=chang[id[sa[i]]]) tmp=mid,r=mid-1;
                else l=mid+1;
            }
            que[id[sa[i]]].L=tmp;
            
            l=i+1,r=n;
            tmp=i;
            while(l<=r){
                int mid=(l+r)>>1;
                if(query(i,mid)>=chang[id[sa[i]]]) tmp=mid,l=mid+1;
                else r=mid-1;
            }
            que[id[sa[i]]].R=tmp;
        }
    }
}
int ans1[N];
int ans2[N];
struct arraytree{
    int f[N];
    void add(int x,int c){
        for(;x<=tot;x+=x&(-x)) f[x]+=c;
    }
    int query(int x){
        int ret=0;
        for(;x;x-=x&(-x)) ret+=f[x];
        return ret;        
    }
}t;
int main(){
    rd(n);rd(m);
    int len,x;
    tot=0;
    for(reg i=1;i<=n;++i){
        rd(len);
        for(reg j=1;j<=len;++j){
            rd(x);
            s[++tot]=x;
            id[tot]=0;
            pos[tot]=i;
        }
        s[++tot]=C;//warning!!!
        pos[tot]=-1;//warning!! -1
        rd(len);
        for(reg j=1;j<=len;++j){
            rd(x);
            s[++tot]=x;
            id[tot]=0;
            pos[tot]=i;
        }
        s[++tot]=C;
        pos[tot]=-1;
    }
    for(reg i=1;i<=m;++i){
        rd(len);
        chang[i]=len;
        for(reg j=1;j<=len;++j){
            rd(x);
            s[++tot]=x;
            pos[tot]=0;
            if(j==1) id[tot]=i;
        }
        s[++tot]=C;
        pos[tot]=-1;
    }
    SA(tot);
    
    HEI(tot);
    
    prewrk(tot);
    
    
//    cout<<" after "<<endl;
//    
    sort(que+1,que+m+1);
//    
//    for(reg i=1;i<=tot;++i){
//        cout<<s[i]<<" ";    
//    }cout<<endl<<endl;
////    
//    for(reg i=1;i<=tot;++i){
//        cout<<pos[sa[i]]<<" ";
//    }cout<<endl<<endl;
////    
//    for(reg i=1;i<=tot;++i){
//        cout<<id[sa[i]]<<" ";
//    }cout<<endl<<endl;
//    for(reg i=1;i<=tot;++i){
//        cout<<hei[i]<<" ";
//    }cout<<endl<<endl;
    
    for(reg i=1;i<=m;++i){
    //    cout<<que[i].L<<" "<<que[i].R<<" "<<que[i].id<<endl;
        po[++cnt].c=1;
        po[cnt].L=que[i].L,po[cnt].p=que[i].L;
        
        po[++cnt].c=-1;
        po[cnt].L=que[i].L,po[cnt].p=que[i].R;
    }
    sort(po+1,po+cnt+1);
    
    for(reg i=1;i<=tot;++i){
        if(pos[sa[i]]>0){
            pre[i]=las[pos[sa[i]]];
            las[pos[sa[i]]]=i;
        }
    }
    memset(las,0,sizeof las);
    for(reg i=tot;i>=1;--i){
        if(pos[sa[i]]>0){
            if(las[pos[sa[i]]])nxt[i]=las[pos[sa[i]]];
            else nxt[i]=tot+1;
            las[pos[sa[i]]]=i;
        }
    }
    int now=1;
    for(reg i=1;i<=n;++i){
        if(las[i]){
            //cout<<i<<" add fir "<<las[i]<<endl;
            t.add(las[i],1);
        }
    }
    for(reg i=1;i<=tot;++i){
        while(now<=m&&que[now].L==i){
            //<<que[now].id<<" : "<<que[now].R<<" "<<t.query(que[now].R)<<" "<<que[now].L<<" "<<t.query(que[now].L-1)<<endl;
            que[now].ans=t.query(que[now].R)-t.query(que[now].L-1);
            ans1[que[now].id]=que[now].ans;
            ++now;
        }
        if(nxt[i]&&nxt[i]<=tot) t.add(nxt[i],1);
    }
    memset(t.f,0,sizeof t.f);
    now=1;
    for(reg i=1;i<=tot;++i){
        while(po[now].p==i&&now<=cnt&&po[now].c==1){
            t.add(po[now].L,po[now].c);
            ++now;
        }
        if(pos[sa[i]]>0){
            ans2[pos[sa[i]]]+=t.query(i)-t.query(pre[i]);
        }
        while(po[now].p==i&&now<=cnt){
            t.add(po[now].L,po[now].c);
            ++now;
        }
    }
    for(reg i=1;i<=m;++i){
        printf("%d\n",ans1[i]);
    }
    for(reg i=1;i<=n;++i){
        printf("%d ",ans2[i]);
    }
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/23 11:39:01
*/
View Code

 

法七:字尾陣列+主席樹+樹狀陣列

emmm。。。

第一問也可以用主席樹做。。。(雖然已經離線了,就完全沒有必要了)

法八:字尾自動機+暴力

SAM大吼一聲,怎麼能少了俺?!?!

對所有的姓、名建廣義SAM

可以對每個串的每個位置暴力跳parent樹,把每個right集合的位置實際在多少個串上出現,叫做sz,記錄下來。

詢問的話,匹配一遍,如果中途沒有失配,最後的匹配到節點的sz即為答案。

然後在這個點上打上tag++

最後,把所有的姓名串的每個位置再跑一遍,tag的總和就是被點名次數。當然,要在途中留下自己的標記,以防重複統計。

還是暴力。

一串111111應該還是可以卡掉?因為parent樹退化成了一條鏈。

但是值得一提的是,這個演算法總算是線上的!

法九:字尾自動機+莫隊

思路來自:ywy_c_asm

莫隊支援區間,那字尾自動機哪裡有區間?

 

先把每個詢問串在後綴自動機上跑一下,(失配直接puts0,然後滾蛋)

最後到了某個節點p,那麼根據parent樹的意義,p的子樹中的所有點代表的位置都包含這個詢問串!

於是,我們用莫隊來搞dfn序!(具體所屬情況,葉子就記錄了。)

然後的方法大家就已經很熟悉了。(當然也可以用主席樹或者樹狀陣列做。)