題目描述
如題,給定N個字符串(第i個字符串長度為Mi,字符串內包含數字、大小寫字母,大小寫敏感),請求出N個字符串中共有多少個不同的字符串。
友情提醒:如果真的想好好練習哈希的話,請自覺,否則請右轉PJ試煉場:)
輸入輸出格式
輸入格式:
第一行包含一個整數N,為字符串的個數。
接下來N行每行包含一個字符串,為所提供的字符串。
輸出格式:
輸出包含一行,包含一個整數,為不同的字符串個數。
輸入輸出樣例
5 abc aaaa abc abcc 12345
4
說明
時空限制:1000ms,128M
數據規模:
對於30%的數據:N<=10,Mi≈6,Mmax<=15;
對於70%的數據:N<=1000,Mi≈100,Mmax<=150
對於100%的數據:N<=10000,Mi≈1000,Mmax<=1500
樣例說明:
樣例中第一個字符串(abc)和第三個字符串(abc)是一樣的,所以所提供字符串的集合為{aaaa,abc,abcc,12345},故共計4個不同的字符串。
Tip: 感興趣的話,你們可以先看一看以下三題:
BZOJ3097:http://www.lydsy.com/JudgeOnline/problem.php?id=3097
BZOJ3098:http://www.lydsy.com/JudgeOnline/problem.php?id=3098
BZOJ3099:http://www.lydsy.com/JudgeOnline/problem.php?id=3099
如果你仔細研究過了(或者至少仔細看過AC人數的話),我想你一定會明白字符串哈希的正確姿勢的^_^
Solution:
首先你得會寫鏈式前向星(即鏈表/鄰接表,常用於存儲圖)。將一個字符串看成255進制數,然后把它轉成十進制數,並找一個大質數對該十進制數取模。這里你也可以使用秦九韶算法(很簡單,可以百度,名字高端了一點;如果不會你怎么轉的k到10進制就怎么轉233),就能獲得這個字符串的哈希值了。
然后如何判重?兩個字符串的哈希值相同就證明這兩個字符串相同——原本應該是這樣的。但由於取模,確實可能出現兩個字符串hash值出現重復的情況。這時我們就需要套個鄰接表,給每個哈希值下的字符串都判斷一遍。如果沒有字符串相同,再往這個哈希值下插入這個字符串,並計數加一。事實證明,哈希值沖突的情況不多,如果鏈式前向星寫得熟那更是萬無一失。Luogu評測耗時為500ms左右,比很多評測記錄還是快很多。如果只是一般的Hash,我覺得用這個基本上就夠了的說....=、=想辦法把要記錄的狀態轉成k進制數,再轉成10進制數對大質數取模,然后套進鄰接表來判重。代碼復雜度不高,而且效率也不差。
怎么把字符串在鄰接表里套進同一哈希值?嗯....你把它看作圖論里的插邊。我覺得這兩種東西差不多。
所謂的大質數....嗯嗯,我不管那么多的,我直接1000013就做了。如果想要靠譜一點的大質數....百度哇。
下面是代碼。
#include<bits/stdc++.h> using namespace std; const int MOD=1e6+13,N=10010; int h[MOD],nexp[N],p=1; string s[N];//鏈式前向星 int getHash(string x){ int plus=255,ret=x[0],len=x.size(); for(int i=1;i<len;i++){ ret=(ret*plus)%MOD; ret+=x[i]; } return ret%MOD; }//獲取字符串的哈希值 bool insHash(string x){ int c=getHash(x); for(int u=h[c];u;u=nexp[u]){ if(s[u]==x)return 0;//若發現重復則返回0 } nexp[p]=h[c],h[c]=p,s[p]=x,p++; return 1;//否則插入 } int main(){ std::ios::sync_with_stdio(false); //相關內容可以百度搜索,可以加快cin效率 int n; cin>>n; string a; int ans=0; for(int i=0;i<n;i++){ cin>>a; if(insHash(a))ans++; } cout<<ans; return 0; }