HASH刷題整理
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
- 先預處理出HASH值,然後枚舉長度,然後驗證一遍即可。
- 或者不用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
,既是前綴又是後綴的:ab
,abab
,ababcabab
,ababcababababcabab
。
輸入格式
輸入若幹行,每行一個字符串。
輸出格式
對於每個字符串,輸出一行,包含若幹個遞增的整數,表示所有既是前綴又是後綴的子串長度。
樣例
樣例輸入
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刷題整理