[CTSC2014] 企鵝 QQ——雜湊

Glowingfire發表於2024-10-13

[CTSC2014] 企鵝 QQ

題目背景

PenguinQQ 是中國最大、最具影響力的 SNS(Social Networking Services)網站,以實名制為基礎,為使用者提供日誌、群、即時通訊、相簿、集市等豐富強大的網際網路功能體驗,滿足使用者對社交、資訊、娛樂、交易等多方面的需求。

題目描述

小 Q 是 PenguinQQ 網站的管理員,他最近在進行一項有趣的研究——哪些賬戶是同一個人註冊的。經過長時間的分析,小Q發現同一個人註冊的賬戶名稱總是很相似的,例如 Penguin1,Penguin2,Penguin3……於是小 Q 決定先對這種相似的情形進行統計。

小 Q 定義,若兩個賬戶名稱是相似的,當且僅當這兩個字串等長且恰好只有一位不同。例如“Penguin1”和“Penguin2”是相似的,但“Penguin1”和“2Penguin”不是相似的。而小 Q 想知道,在給定的 \(n\) 個賬戶名稱中,有多少對是相似的。

為了簡化你的工作,小Q給你的N 個字串長度均等於L ,且只包含大小寫字母、數字、下劃線以及‘@’共64種字元,而且不存在兩個相同的賬戶名稱。

輸入格式

第一行包含三個正整數 \(N,L,S\)。其中 \(N\) 表示賬戶名稱數量,\(L\) 表示賬戶名稱長度,\(S\) 用來表示字符集規模大小,它的值只可能為 \(2\)\(64\)

\(S\) 等於 \(2\),賬戶名稱中只包含字元 01\(2\) 種字元;

\(S\) 等於 \(64\),賬戶名稱中可能包含大小寫字母、數字、下劃線以及 @\(64\) 種字元。

隨後 \(N\) 行,每行一個長度為 \(L\) 的字串,用來描述一個賬戶名稱。資料保證 \(N\) 個字串是兩兩不同的。

輸出格式

僅一行一個正整數,表示共有多少對相似的賬戶名稱。

樣例 #1

樣例輸入 #1

4 3 64
Fax
fax
max
mac

樣例輸出 #1

4

提示

\(4\) 對相似的字串分別為:Fax 與 fax,Fax 與 max,fax 與 max,max 與 mac。

測試點編號 \(N\) \(L\) \(S\)
\(1\) \(50\) \(10\) \(64\)
\(2\) \(500\) \(100\) \(64\)
\(3\) \(3000\) \(100\) \(2\)
\(4\) \(3000\) \(100\) \(64\)
\(5\) \(30000\) \(50\) \(2\)
\(6\) \(30000\) \(50\) \(64\)
\(7\) \(30000\) \(200\) \(2\)
\(8\) \(30000\) \(200\) \(64\)
\(9\) \(30000\) \(200\) \(2\)
\(10\) \(30000\) \(200\) \(64\)

分析

注意到符合題意的字串去掉不同的一位後所形成的字串是相同的。

對於每個字串,計算其去掉某一位後所形成的字串的雜湊值。

這樣會產生至多\(N*L\)個雜湊值,排序後可輸出答案。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100,INF=0x7fffffff;
typedef unsigned long long ull;
ull key=137,bas[N];
char s[30010];
int n,len,siz,cnt;
ull all[6000100],h[30010];
void init()
{
    scanf("%d%d%d",&n,&len,&siz);bas[0]=1;
    for(int i=1;i<=len;++i)
        bas[i]=bas[i-1]*key;
    ull pre,bak;
    for(int i=1;i<=n;++i)
    {
        scanf("%s",s+1);
        for(int j=1;j<=len;++j)
        {
            h[j]=0;
            h[j]=h[j-1]*key+s[j];
        }
        for(int j=1;j<=len;++j)
        {
            //1~j-1
            pre=h[j-1];
            //j+1~len
            bak=(h[len]-h[j]*bas[len-j])*bas[j];
            all[++cnt]=pre+bak;
        }
    }
    sort(all+1,all+1+cnt);
    int st=1,ans=0;
    for(int i=2;i<=cnt;++i)
    {
        if(all[i-1]==all[i])++st;
        else {ans+=st*(st-1)/2;st=1;}
    }
    ans+=st*(st-1)/2;cout<<ans;
}
int main()
{
    init();
    return 0;
}