1. 程式人生 > >BZOJ 3097 Hash Killer I

BZOJ 3097 Hash Killer I

多少 結束 font 次方 target buaa 排序 方便 sea

3097: Hash Killer I

Description

這天天氣不錯,hzhwcmhf神犇給VFleaKing出了一道題: 給你一個長度為N的字符串S,求有多少個不同的長度為L的子串。 子串的定義是S[l]、S[l + 1]、... S[r]這樣連續的一段。 兩個字符串被認為是不同的當且僅當某個位置上的字符不同。

VFleaKing一看覺得這不是Hash的裸題麽!於是果斷寫了哈希 + 排序。 而hzhwcmhf神犇心裏自然知道,這題就是後綴數組的height中 < L的個數 + 1,就是後綴自動機上代表的長度區間包含L的結點個數,就是後綴樹深度為L的結點的數量。 但是hzhwcmhf神犇看了看VFleaKing的做法表示非常汗。於是想卡掉他。

VFleaKing使用的是字典序哈希,其代碼大致如下: u64 val = 0; for (int i = 0; i < l; i++) val = val * base + s[i] - ‘a‘; u64是無符號int64,範圍是[0, 2^64)。VFleaKing讓val自然溢出。 base是一個常量,VFleaKing會根據心情決定其值。 VFleaKing還求出來了base ^ l,即base的l次方,這樣就能方便地求出所有長度為L的子串的哈希值。 然後VFleaKing給哈希值排序,去重,求出有多少個不同的哈希值,把這個數作為結果。 其算法的C++代碼如下:

typedef unsigned long long u64;

const int MaxN = 100000;

inline int hash_handle(const char *s, const int &n, const int &l, const int &base) { u64 hash_pow_l = 1; for (int i = 1; i <= l; i++) hash_pow_l *= base;

int li_n = 0; static u64 li[MaxN];

u64 val = 0; for (int i = 0; i < l; i++) val = val * base + s[i] - ‘a‘; li[li_n++] = val; for (int i = l; i < n; i++) { val = val * base + s[i] - ‘a‘; val -= (s[i - l] - ‘a‘) * hash_pow_l; li[li_n++] = val; }

sort(li, li + li_n); li_n = unique(li, li + li_n) - li; return li_n; }

hzhwcmhf當然知道怎麽卡啦!但是他想考考你。

Input

沒有輸入。

Output

你需要輸出一組數據使得VFleaKing的代碼WA掉。我們會使用Special Judge檢查你的結果的正確性。 輸出文件共兩行。 第一行兩個用空格隔開的數n、l。 第二行是一個長度為n的字符串。只能包含‘a‘~‘z‘。 需要保證1 <= n <= 10^5, 1 <= l <= n, 不符合以上格式會WA。 不要有多余字符,很可能導致你WA。

Sample Input

沒有

Sample Output

8 4 buaabuaa (當然這個輸出是會WA的)

HINT

orz 波蘭人 & fotile96 & sillycross

Source

VFleaKing & hzhwcmhf


  這是一個很強大的系列。

  3097: Hash Killer I  base看心情,unsigned long long自然溢出  Submit: 951 Solved: 356

  3098: Hash Killer II  base看心情,MOD1000000007  Submit: 1597 Solved: 838

  3099: Hash Killer III  base看心情,MOD雙組隨機質數  Submit: 605 Solved: 0

  昨天做了3098,當時直接把N、L開大,輸出100000個隨機數,就卡了過去。本人堅信與3097是相似的,於是重復交了一波,全部WA了。本人表示很傷心。

  最後發現(以下來自http://blog.csdn.net/regina8023/article/details/43112899):  

  非常神奇的構造題。

  首先明白兩點:

    1.卡hash的關鍵在於構造兩個不同的串對應的hash值相同。

    2.爆u64相當於對2^64這個數取模。

  如果base是偶數,那麽a.........aaa(>64個a)與ba.......aa(a的數量為前面那麽串a的數量-1),這兩個串長度相同,hash值相同,顯然串是不同的,這樣就卡掉了。

  如果base是奇數,就比較麻煩了。

  看vfk的做法吧:

  如果base是奇數的話,現在只考慮a、b兩個字母。

  a \ b表示a能整除b。(orz 具體數學)

  設數學上的函數not(S)表示把字符串S中每個位置的‘a‘變成‘b‘,把‘b‘變成‘a‘後形成的字符串。比如not("ababaa") = "bababb"

  strA . strB代表字符串串聯。如"娃" . "哈哈" = "娃哈哈"

  |str|表示字符串str的長度。

  設字符串序列{orzstr[i]}orzstr[1] = "a", orzstr[i] = orzstr[i - 1] . not(orzstr[i - 1])

  那麽|orzstr[i]| = |orzstr[i - 1]| * 2。顯然這是等比數列,得到:|orzstr[i]| = |orzstr[1]| . 2 ^ (i - 1) = 2 ^ (i - 1)

  設hash(str)為str的哈希值。

  則:

  hash(orzstr[i]) = hash(orzstr[i - 1]) * base ^ |not(orzstr[i - 1])| + hash(not(orzstr[i - 1]))

   = hash(orzstr[i - 1]) * base ^ (2 ^ (i - 2)) + hash(not(orzstr[i - 1]))

  hash(not(orzstr[i])) = hash(not(orzstr[i - 1])) * base ^ (2 ^ (i - 2)) + hash(orzstr[i - 1])

  兩式相減:

  hash(orzstr[i]) - hash(not(orzstr[i]))

  = (hash(orzstr[i - 1]) * base ^ (2 ^ (i - 2)) + hash(not(orzstr[i - 1]))) - (hash(not(orzstr[i - 1])) * base ^ (2 ^ (i - 2)) + hash(orzstr[i - 1]))

  = (hash(orzstr[i - 1]) - hash(not(orzstr[i - 1]))) * (base ^ (2 ^ (i - 2)) - 1)

  這讓我們發現,hash(orzstr[i]) - hash(not(orzstr[i]))似乎是個神奇的東西。而我們的目的實際上是要找兩個字符串strA, strB使得

  hash(strA) % 2^64 = hash(strB) % 2^64

  相當與

  2^64 \ hash(strA) - hash(strB)

  設數列{f[i]},f[i] = hash(orzstr[i]) - hash(not(orzstr[i]))

  這樣就有:

  f[i] = f[i - 1] * (base ^ (2 ^ (i - 2)) - 1)

  還是有點不爽啊……我們再設數列{g[i]},g[i] = base ^ (2 ^ (i - 1)) - 1

  於是能寫成:

  f[i] = f[i - 1] * g[i - 1]

  則f[i] = f[1] * g[1] * g[2] * ... * g[i - 1]

  然後發現一個神奇的事情?

  base是奇數,則base的任意正整數次方也一定是奇數。所以對於任意的i必有g[i]為偶數,所以2 ^ (i - 1) \ f[i]

  問題是不是結束了呢……發現沒有……這樣的話我們要使2 ^ 64 \ f[i],至少得讓i = 65……然後發現|orzstr[65]|是個天文數字。

  發現我們剛才那樣分析太坑爹了……

  i > 1時有:

  g[i] = base ^ (2 ^ (i - 1)) - 1 = (base ^ (2 ^ (i - 2)) - 1) * (base ^ (2 ^ (i - 2)) + 1) = g[i - 1] * 一個偶數

  而g[1]顯然是偶數吧……

  那麽4 \ g[2],8 \ g[3]...

  也就是說2 ^ i \ g[i]

  所以f[i] 實際上有:

  (2 ^ 1) * (2 ^ 2) * (2 ^ 3) * ... * (2 ^ (i - 1)) \ f[i]

  2 ^ (i * (i - 1) / 2) \ f[i]

  當i取12時,就有66個2了喲!

  這就是卡base為奇數時的方法。orzstr[12]和not(orzstr[12])即為所求。

  而讀入中base既有奇數又有偶數,直接在奇數構造的字符串後面加64個a就可以了。

  這樣看來,忽然發現,2^64遠遠大於1000000007,所以卡起來不是那麽簡單。那麽,對於3099,想要AC談何容易。

  

技術分享
 1 /************************************************************** 
 2     Problem: 3097 
 3     User: Doggu 
 4     Language: C++ 
 5     Result: Accepted 
 6     Time:0 ms 
 7     Memory:916 kb 
 8 ****************************************************************/
 9   
10 #include <cstdio> 
11 #include <algorithm> 
12 char s[100000]; 
13 int main() { 
14     printf("%d %d\n",(1<<11)+65,(1<<10)); 
15     int now=1;s[1]=a; 
16     for( int i = 1; i <= 11; i++ ) { 
17         for( int j = 1; j <= now; j++ ) 
18             s[j+now]=s[j]==a?b:a; 
19         now<<=1; 
20     } 
21     for( int i = 1; i <= now; i++ ) putchar(s[i]); 
22     for( int i = 1; i <= 65; i++ ) putchar(a); 
23     putchar(\n); 
24     return 0; 
25 } 
26 
27 
構造

BZOJ 3097 Hash Killer I