1. 程式人生 > >HASH刷題整理

HASH刷題整理

pow 計數 n+1 又是 範圍 a* strings 循環 code

HASH刷題整理

  • 簡單整理了下自己學習hash時做的題

[poj3461]Oulipo

題目描述

這是一道模板題。

給定一個字符串A和一個字符串B,求B在A中的出現次數。A和B中的字符均為英語大寫字母或小寫字母。

A中不同位置出現的B可重疊。

輸入格式

輸入共兩行,分別是字符串A和字符串B。

輸出格式

輸出一個整數,表示B在A中的出現次數。

樣例

樣例輸入

3
BAPC
BAPC
AZA
AZAZAZA
VERDI
AVERDXIVYERDIAN

樣例輸出

1
3
0

數據範圍與提示

\(1≤A,B 的長度 \leq 10 ^ 6\) 僅包含大小寫字母。

Solution

模板題……

#include <cstdio>
#include <cstring>
#include <cmath>
#define MAXN 1000005
#define base 107

char a[MAXN],b[MAXN];
unsigned long long h[MAXN];
unsigned long long power[MAXN];
unsigned long long counta;
int len_a;
int len_b;
int T;

int main(){

    power[0] = 1;
    for(register int i=1;i<MAXN;++i)power[i] = power[i-1]*base;

    scanf("%d",&T);
    for(register int k=1;k<=T;++k){
        
        scanf("%s%s",a,b);
        
        counta = 0;
        len_a = std::strlen(a);
        len_b = std::strlen(b);

        for(register int i=0;i<len_a;++i)
            counta = counta*base + (a[i]-'A'+1);

        h[0] = b[0]-'A'+1;
        for(register int i=1;i<len_b;++i)
            h[i] = h[i-1]*base + b[i] - 'A' + 1;

        int ans = 0;
        if(counta==h[len_a-1])ans++;

        for(register int i=0;i+len_a<len_b;++i){
            if(counta==h[i+len_a] - h[i]*power[len_a])ans++;
        }

        printf("%d\n",ans);
    }
    return 0;
}

[poj2406]Power Strings

題目描述

給定若幹個長度\(\leq 10^6\)的字符串,詢問每個字符串最多是由多少個相同的子字符串重復連接而成的。如:ababab 則最多有 3個 ab 連接而成。

輸入格式

輸入若幹行,每行有一個字符串,字符串僅含英語字母。特別的,字符串可能為 . 即一個半角句號,此時輸入結束。

樣例

樣例輸入

abcd
aaaa
ababab
.

樣例輸出

1
4
3

數據範圍與提示

字符串長度 \(\leq 10^6\)

Solution

  1. 先預處理出HASH值,然後枚舉長度,然後驗證一遍即可。
  2. 或者不用HASH做,直接枚舉長度進行驗證。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define base 1e4+7
#define MAXN 1000005

char s[MAXN];
inline bool C(int len,int size){
    for(register int i=0;i<len;++i){
        for(register int j=i;j<size;j+=len)
            if(s[j]!=s[i])return false;
    }
    return true;
}
int main(){

    while(true){
        
        scanf("%s",s);
        if(s[0]=='.')break;

        int len = std::strlen(s);
        int ans = 1;
        for(register int i=1;i<len;++i){
            if(len%i!=0)continue;
            if(C(i,len)){
                ans = len / i;
                break;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

[poj2752]Seek the Name, Seek the Fame

題目描述

給定若幹字符串(這些字符串總長 \(\leq 4 \times 10^5\)),在每個字符串中求出所有既是前綴又是後綴的子串長度。

例如:ababcababababcabab,既是前綴又是後綴的:abababababcababababcababababcabab

輸入格式

輸入若幹行,每行一個字符串。

輸出格式

對於每個字符串,輸出一行,包含若幹個遞增的整數,表示所有既是前綴又是後綴的子串長度。

樣例

樣例輸入

ababcababababcabab
aaaaa

樣例輸出

2 4 9 18
1 2 3 4 5

Solution

正著HASH一遍,反正HASH一遍,然後枚舉長度,比較是否相等並計數即可。

#include <cstdio>
#include <cstring>
#define MAXN 400005
#define base (unsigned long long)(1e5+7)

char s[MAXN];
unsigned long long power[MAXN];
unsigned long long C[MAXN];

int main(){

    power[0] = 1;
    for(register int i=1;i<MAXN;++i){
        power[i] = power[i-1]*base;
    }

    while(scanf("%s",s)!=EOF){
        
        int len = std::strlen(s);

        C[0] = 0;
        for(register int i=0;i<len;++i){
            C[i] = C[i-1>=0?i-1:0]*base + s[i] - 'a' + 1;
        }

        for(register int i=1;i<=len;++i){
            unsigned long long a = C[i-1];
            unsigned long long b = C[len-1] - (len-i-1>=0?C[len-i-1]:0)*power[i];
            if(a==b)printf("%d ",i);
        }
        puts("");
    }
    return 0;
}

[bzoj3916]Friends

題目描述

原題來自:BalticOI 2014

有三個好朋友喜歡在一起玩遊戲,A 君寫下一個字符串 S,B 君將其復制一遍得到T,C 君在 T 的任意位置(包括首尾)插入一個字符得到 U。現在你得到了 U,請你找出S。

輸入格式

第一行一個數 N,表示U 的長度。 第二行一個字符串U,保證U 由大寫字母組成。

輸出格式

輸出一行,若 S不存在,輸出 NOT POSSIBLE。若S不唯一,輸出 NOT UNIQUE,否則輸出 S。

樣例

樣例輸入 1

7
ABXCABC

樣例輸出 1

ABC

樣例輸入 2

6
ABCDEF

樣例輸出 2

NOT POSSIBLE

樣例輸入 3

9
ABABABABA

樣例輸出 1

NOT UNIQUE

數據範圍與提示

\(2 \leq N \leq 2000001\)

Solution

枚舉插入的字符,然後截斷比較一下,進行計數即可,這裏要特別註意若在不同的地方插入字符,最後截出來的字符串如果是一樣的,則算同一個解。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define base (unsigned long long)(1e5+7)
#define MAXN 2000005
 
char s[MAXN];
unsigned long long power[MAXN];
unsigned long long C[MAXN];
unsigned long long lastans = 0;
int N;
 
int main(){
 
    scanf("%d\n",&N);
    scanf("%s",s+1);
 
    power[0] = 1;
    for(register int i=1;i<MAXN;++i){
        power[i] = power[i-1]*base;
    }
 
    if(N&1==0||N<=1){
        puts("NOT POSSIBLE");
        return 0;
    }
 
    int mid = (N+1) >> 1;
 
    C[0] = 0;
    for(register int i=1;i<=N;++i){
        C[i] = C[i-1]*base + s[i] - 'A' + 1;
    }
 
    int ans = 0;
    int count = 0;
    unsigned long long a,b;
 
    for(register int i=1;i<=N;++i){
        if(i==mid){
            a = C[mid-1];
            b = C[N] - C[mid]*power[N-mid];
        }
        else if(i<mid){
            a = C[i-1]*power[(mid-1)-(i-1)] + C[mid] - C[i]*power[mid-i];
            b = C[N] - C[mid]*power[N-mid];
        }
        else{
            a = C[mid-1];
            b = (C[i-1] - C[mid-1]*power[(i-1)-(mid-1)])*power[(mid-1)-(i-mid)] + (C[N] - C[i]*power[N-i]);
        }
        if(a==b){
            ans = i;
            count++;
            if(count==1)lastans = a;
            if(count>1){
                if(a==lastans)count--;
                else break;
            }
        }
    }
 
    if(count==0){
        puts("NOT POSSIBLE");
        return 0;
    }
    if(count>1){
        puts("NOT UNIQUE");
        return 0;
    }
    count = 0;
    for(register int i=1;i<=N;++i){
        if(i==ans)continue;
        putchar(s[i]);
        if((++count)==mid-1)break;
    }
    return 0;
}

[luogu3498] POI2010 KOR-Beads

題目描述

Byteasar 決定制造一條項鏈,她買了一串珠子,她有一個機器,能把這條珠子切成很多段,使得每段恰有 k個珠子 (k>0),如果這條珠子的長度不是k的倍數,最後一塊長度小於k的段就被丟棄了。
Byteasar 想知道,選擇什麽數字k 可以得到最多的不同的段。註意這裏的段是可以反轉的,即,子串 1,2,3 和 3,2,1 被認為是一樣的。

輸入格式

第一行一個正整數n,表示珠子的長度。
第二行 n個空格隔開的正整數 \(a_1,a_2,?a_n\) ,描述這一串珠子的顏色。

輸出格式

第一行兩個空格隔開的正整數,第一個表示能獲得的最大不同的段的個數,第二個表示能獲得最大值的k 的個數。
第二行若幹空格隔開的正整數k,表示所有能夠取得最大值的k ,請將k按照從小到大的順序輸出。

樣例

輸入樣例

21
1 1 1 2 2 2 3 3 3 1 2 3 3 1 2 2 1 3 3 2 1

輸出樣例

6 1
2

數據範圍與提示

\(1 \leq n \leq 200000,1 \leq a_i \leq n\)

Solution

先預處理正反的HASH,然後枚舉長度,開始計算個數即可。

乍一看是超時的\(O(N^2)\)算法,但是考慮到每次裏層的循環都是\(N/i\)次,便有$1 + \frac{1}{2} + \frac{1}{3} + ... + \frac{1}{N} \(,是遠遠到不了\)O(N^2)$的。

#include <cstdio>
#include <cstring>
#define MAXN 200005
#define base (unsigned long long)10007
#define MOD 1000007

unsigned long long power[MAXN];
unsigned long long Pre[MAXN];
unsigned long long Suf[MAXN];
int a[MAXN];
int ans[MAXN];

struct Node{
    int vis;
    unsigned long long hash;
}H[MOD];

int N,tot=0;

inline void add(unsigned long long x,int len){
    int hash = (int)((x%MOD+MOD)%MOD);
    while(H[hash].vis==len){
        if(H[hash].hash==x)return;
        hash = hash+1==MOD ? 0 : hash+1;
    }
    H[hash].vis = len;
    H[hash].hash = x;
}

inline bool live(unsigned long long x,int len){
    int hash = (int)((x%MOD+MOD)%MOD);
    while(H[hash].vis==len){
        if(H[hash].hash==x)return true;
        hash = hash+1==MOD ? 0 : hash+1;
    }
    return false;
}
int main(){

    scanf("%d",&N);
    for(register int i=1;i<=N;++i){
        scanf("%d",&a[i]);
    }
    std::memset(H,0,sizeof(H));

    Pre[0] = 0;
    for(register int i=1;i<=N;++i){
        Pre[i] = Pre[i-1]*base + (unsigned long long)a[i];
    }

    Suf[N+1] = 0;
    for(register int i=N;i>0;--i){
        Suf[i] = Suf[i+1]*base + (unsigned long long)a[i];
    }

    power[0] = 1;
    for(register int i=1;i<=N;++i){
        power[i] = power[i-1]*base;
    }

    int maxx = 0;
    for(register int len=1;len<=N;++len){
        
        int count = 0;
        for(register int i=1;i+len-1<=N;i+=len){
            unsigned long long front = Pre[i+len-1] - Pre[i-1]*power[len];
            unsigned long long back = Suf[i] - Suf[i+len]*power[len];
            if(live(front,len)||live(back,len))continue;
            count++;
            add(front,len);add(back,len);
        }

        if(count>maxx){
            maxx = count;
            tot = 1;
            ans[1] = len;
        }
        else if(count==maxx){
            ans[++tot] = len;
        }
    }

    printf("%d %d\n",maxx,tot);
    for(register int i=1;i<=tot;++i)printf("%d ",ans[i]);
    return 0;
}

[luogu3501] POI2010 ANT-Antisymmetry

題目描述

對於一個01字符串,如果將這個字符串0和1取反後,再將整個串反過來和原串一樣,就稱作“反對稱”字符串。比如00001111和010101就是反對稱的,1001就不是。

現在給出一個長度為N的01字符串,求它有多少個子串是反對稱的。

輸入格式

第一行一個正整數n 。
第二行一個長度為 n的 0/1字符串。

輸出格式

一行一個整數,表示原串的反對稱子串個數。

樣例

樣例輸入

8
11001011

樣例輸出

7

數據範圍與提示

\(1 \leq n \leq 500 000\)

Solution

這題真的好玩,我是沒想出來

對於一個反對稱的01串,則包含在其中的01串也是反對稱的,前提是兩個串的中點一致,所以可以枚舉中點,進行二分查找最遠的可到達的點即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 500005
#define base (unsigned long long)1000007

unsigned long long Pre[MAXN];
unsigned long long Suf[MAXN];
unsigned long long power[MAXN];

char s[MAXN];
int N,tot;

inline bool Check(int l1,int r1,int l2,int r2){
    if(l1<0||r2>N+1)return false;
    unsigned long long a = Pre[r1] - Pre[l1-1]*power[r1-l1+1];
    unsigned long long b = Suf[l2] - Suf[r2+1]*power[r1-l1+1];
    return a==b;
} 
int main(){
    
    scanf("%d\n",&N);
    scanf("%s",s+1);
    
    power[0] = 1;
    for(register int i=1;i<=N;++i){
        power[i] = power[i-1]*base;
    }
    
    Pre[0] = 0;
    for(register int i=1;i<=N;++i){
        Pre[i] = Pre[i-1]*base + s[i] - '0' + 1;
    }
    
    Suf[N+1] = 0;
    for(register int i=N;i>0;--i){
        Suf[i] = Suf[i+1]*base + ((s[i]-'0')^1) + 1;
    }
    
    long long ans = 0;
    for(register int i=1;i<N;++i){
        int l = 0;
        int r = (N>>1);
        while(l<r){
            int mid = (l+r+1)>>1;
            if(Check(i-mid+1,i,i+1,i+mid))l = mid;
            else r = mid-1;
        }
        ans += r;
    }
    printf("%lld",ans);
    return 0;
}

HASH刷題整理