啊~個人速度真的是太慢了,學校又要提早開學!!還有兩個月就要比賽了,垂死掙扎一下吧~html
繼續更新筆記(眼含淚水)正則表達式
1、Trie樹:算法
1.定義:經過字符串建成一棵樹,這棵樹的節點個數必定是最少的。例如:4個字符串"ab","abc","bd","dda"對應的trie樹以下:數組
其中紅色節點表示存在一個字符串是以這個點結尾的。oop
一個性質:在樹上,兩個點u,v知足u是v的祖先,那麼u表明的字符串必定是v表明的字符串的前綴。網站
2.Trie樹的插入:能夠從根節點出發,每次沿着要走的字符串往下走,若沒有則創建新節點。spa
假如全部字符串的長度之和爲n,構建這棵trie樹的時間複雜度爲O(n)。指針
1 int root=1; 2 int cnt=1; 3 int p[i][j];//表示從i這個節點沿着j這個字符走,能走到哪一個點,走不到就是0 4 char s[];//存儲字符串 5 for(int i=1;i<=n;i++) 6 { 7 scanf("%s",s); 8 int len=strlen(s); 9 int now=root;//now表示走到的當前節點,now的初始值爲根節點 10 for(int j=0;j<len;j++) 11 { 12 if(p[now][s[j]]>0){ 13 now=p[now][s[j]];//若是能沿着j節點往下走,直接往下走 14 } 15 else{ 16 pow[now][s[j]]=++cnt; 17 now=p[now][s[j]]; 18 } 19 } 20 v[now]++;//記錄now這個節點被訪問的次數 21 }
3.Trie樹的查詢:能夠看出trie樹中每一個節點表示其中一個字符串的前綴,在作題過程當中每每經過這個性質來獲得較好的時間複雜度。code
4.一個例題:給定n個互不相同的串,求存在多少對數(i,j)(共n2對)知足第i個串是第j個串的前綴。htm
全部串的長度之和≤500000。
解題:根據性質,「在樹上,兩個點u,v知足u是v的祖先,那麼u表明的字符串必定是v表明的字符串的前綴」。
咱們要知足一個串是另外一個串的前綴,也就是說,在trie樹上,這個串對應的位置是另外一個串對應的位置的祖先。
構建這棵trie樹,而後咱們枚舉每一個紅色點,它對答案的貢獻是以它爲根的子樹中紅色節點的個數之和。這個東西能夠在一開始遍歷這棵樹預處理出來!
時間複雜度是線性的。
1 void dfs(int x) 2 { 3 if(v[x]) sum[x]++; 4 for(char i='a';i<='z';i++) 5 { 6 if(p[x][i])//從當前x沿着i這個字符走還能往下走 7 { 8 dfs(p[x][i]);//往下走 9 sum[x]+=sum[p[x][i]];//累加貢獻sum 10 } 11 } 12 } 13 dfs(1);//從根節點開始
5.USACO的某題(這題真的亂):給定n個串,重排字符之間的大小關係,問哪些串有可能成爲字典序最小的串。
全部字符串的長度之和<=100000。
例如,有一個字符串,裏面只有'a'~'z'這些字符,默認地,'a'是最小的,'z'是最大的。可是咱們能夠從新定義字符間的大小關係,好比這樣:b<c<d<y<x<z,從而咱們對於一些字符串,就按照咱們新定義的大小關係來比較字典序大小。
舉個栗子:
三個字符串"aab","aba","baa",總共只出現了兩個字符'a'和'b',因此字符間的大小關係要麼是a<b要麼是a>b,假設a<b,則第一個串"aab"就是字典序最小的串;假設b<a,則第三個串"baa"就是字典序最小的串,可是對於第二個串,不管咱們怎麼定義字符間的大小關係,都不可能成爲三個字符串中字典序最小的。
解題:首先對這n個串構建trie樹,以後對每一個串,從根走向它,這個路程中遇到的全部兄弟在字典序下都比它大。
對於每一個串,從前日後,直接肯定這個字符的大小關係,判斷是不是字典序最小。
如下兩個串都是有可能成爲字典序最小的串的:
abcd…xyza
abcd…xyzb
因此有:u是v的前綴,則v必定不是字典序最小的串。
如今問題來了:對於每一個串,在什麼條件下,是字典序最小的??
來個栗子:有一些字符串("aabc","aac","aad","ac","adb","aabb"),構造trie樹以下:
對於"aabc"這個串,往下遍歷:
從深度爲2的那一層,咱們能夠獲得:a<c,a<d;
從深度爲3的那一層,咱們能夠獲得:b<c,b<d;
從深度爲4的那一層,咱們還能夠獲得:c<b。
綜上所述:咱們獲得了一堆的關係:a<c,a<d;b<c,b<d;c<b,容易看出,這些關係是存在矛盾的,因此無解->"aabc"是不可能成爲字典序最小的串。
因此要想有解,這一堆的關係必須知足兩個條件:①不存在矛盾②不存在環
總的來講,就是判斷這一堆的關係是否存在拓撲序!
最多有26×26個大小關係,而最多有100000個字符串,因此時間複雜度最大爲:O(26×26×100000)。
2、KMP算法(「看mao片算法」~咳咳):
給定兩個字符串A,B,判斷T是否爲S的子串(變式:尋找子串B在串A中的位置)。
要求一個O(|A|+|B|)的作法。
一般稱A爲目標串(或主串),B爲模式串。
算法過程:
咱們假設串A的長度爲n,串B的長度爲m,每一個字符串的開頭下標默認爲1。
定義兩個變量i和j,這兩個變量共同表示:A[i-j+1~i]與B[1~j]均匹配,即:A中以第i個字符結尾的、長度爲j的字符串,和B從頭開始長度爲j的字符串徹底匹配。
繼續往下匹配:若是i+1和j+1不匹配。
如今,就是用到了KMP算法的核心:它對這一狀況的處理方式是減小j,就至關於將子串向右平移。
平移的目的是爲了讓「A[i-j+1~i]與B[1~j]均匹配」這個條件從新知足。
在上圖中,j一直減少到了0,由於向右平移的過程當中,始終不能讓這個條件知足(最右邊"?"部分已經越界)
但有時候,將j減小一點點以後,是能夠從新知足條件的,例如:
那麼咱們將j從7減少到4時,有:
這樣就能夠徹底匹配啦!可是後面還有沒有匹配的機會咱們就無論了,至少咱們已經保證A[4~7]和B[1~4]徹底匹配上了。
如今考慮一個問題:咱們每次把j減少1(一位一位地平移B字符串),這樣太慢了,咱們在這裏預處理一個next[]數組,表示當j匹配不下去的時候,咱們能夠把j減小到next[j],繼續嘗試匹配。
預處理過程:讓j本身和本身匹配一下,一旦匹配發現B[k-m+1~k] 和 B[1~m] 匹配,則說明在A與B匹配過程當中,j等於k匹配不下去時,j能夠嘗試減少到m。
過程以下:
/**************************************///靚麗的分界線
一些代碼:
1 /*核心內容*/ 2 for(int i=1,j=0;i<=n;i++) 3 { 4 while(j&&B[j+1]!= A[i]) j=next[j]; 5 if(B[j+1]==A[i]) j++; 6 if(j==m) 7 { 8 printf("%d\n", i-j+1);//輸出找到的"B字串在 A中位置" 9 //若是要求的是出現次數,這裏也有多是ans++什麼的 10 j=next[j];//讓循環進行下去 11 } 12 }
1 for(int i=2,j=0;i<=m;i++)/*預處理next[]數組*/ 2 { 3 while(j&&B[j+1]!=B[i]) j=next[j]; 4 if(B[j+1]==B[i]) j++; 5 next[i]=j; 6 }
經典例題:Blue jeans(POJ 3080)
給定m個串,求字典序最小的公共子串。找一個串,使得這個串是全部串的子串,而且字典序最小。
m≤10,每一個串的長度≤60.
解題思路:一個串,若是是全部串的子串,那麼確定是第一個串的子串。
枚舉全部子串,複雜度爲60*60。
驗證其它串是否包含這個子串,複雜度爲10*60。
每次更新答案便可。
時間複雜度爲:O(603×10)
經典例題:Seek the Name,Seek the Fame(POJ 2752)
給定一個字符串S,求全部既是S的前綴又是S的後綴的子串,從小到大輸出這些串的長度。
|S|<=500000。
N爲字符串S的長度。
解題思路:
回到KMP算法,咱們令P[j]表示找最大的數x,使得B中位置是1~x的字符與j-x+1~j的字符徹底相同,也就是上面講的KMP算法中的next[]。
考慮P[|S|]的意義,也就是最大的前綴等於後綴的長度(不包括其自己)。
那P[P[|S|]]就是次大的。
所以全部P[P[…P[|S|]]]就是答案了,一直這樣遞歸下去就能夠找到答案。
或者用Hash來作,這樣更容易想到也比較方便,但效率沒有KMP高。
3、AC自動機:
有n個模式串,長度之和是|T|,有一個主串,長度是|S|,問哪些模式串是這個主串的子串(或者有多少個模式串在主串中出現過)?
解法一:直接跑n次KMP算法,時間複雜度:O(n×|S|)。
解法二:AC自動機,時間複雜度:O(|T|+|S|),對於n個串,構建trie樹,在trie樹上作KMP。
在這裏我來詳解一下AC自動機啊~
首先咱們定義一個指針,叫作「失配指針」或者「失敗指針」,在KMP算法中,這個失配指針就是next[]數組,一樣地在AC自動機中,在trie樹上也定義一個失配指針與此相似但不徹底相同。
失配指針:假設一個節點k的失配指針指向j,那麼k、j知足性質:設根節點root到j的距離爲n,則從k以上的第n個節點到k這個節點所組成的長度爲n的單詞,與根節點root到j所組成的單詞徹底相同。
以下圖:
單詞"she"中的'e'的失配指針指向的是單詞"her"中的'e',由於紅框中的部分是徹底同樣的。
而後,問題來了,咱們該怎樣處理這個失配指針呢?其實咱們能夠用BFS就很方便地解決了。
處理過程:讓和根節點直接相連的節點的失配指針指向根節點,對於其餘節點(假設爲a),設這個節點上的字母爲ch,沿着a的父親b的失配指針走,一直走到一個節點c,c的兒子中也有字母爲ch的節點d,而後把a節點的失配指針指向c節點的兒子d(由於d的字母也爲ch),若是一直走到了根節點都沒找到,那就把失配指針指向根節點。
最開始,咱們把根節點加入隊列(根節點的失敗指針顯然指向本身),這之後咱們每處理一個點,就把它的全部兒子加入隊列,直到搞完。
這樣咱們就獲得了一棵帶有失配指針的trie樹了,接下來正式介紹AC自動機工做原理!
AC自動機原理:對於一棵trie樹,咱們用黃色表示一個單詞(某個模式串)的末尾,也就是說從根節點走到一個黃色的點,就組成一個「單詞」,以下圖:
一開始,trie樹中有一個指針t1指向根節點root,將這n個模式串合併爲一個模式主串,模式主串中有一個指針t2指向這個模式主串的串頭。
接下來進行相似KMP算法的操做:若是t2指向的字母,是trie樹中,t1指向的節點的兒子,那麼把t2+1,t1改成那個兒子的編號,不然t1順這當前節點的失配指針往上找,直到t2是t1的一個兒子,或者t1指向根爲止。
若是t1通過了一個黃色的點,那麼以這個點結尾的單詞就算出現過了(這個模式串已經在主串中出現了),或者若是t1所在的點能夠沿着失配指針走到一個黃色的點,那麼以那個黃色的點爲結尾的單詞就算出現過了(這個模式串已經在主串中出現了),記錄答案便可。
代碼。。。我比較懶並且弱,之後有機會再發吧!
經典例題:僞裝是字符串的題——正則表達式(ZHW YY出來的題)
給定一個字符串,判斷其是否爲合法的正則表達式。
一個正則表達式定義爲:
①0是正則表達式,1也是正則表達式。
②P和Q都是正則表達式,則PQ也是正則表達式。
③P是正則表達式,則(P)是正則表達式
④P是正則表達式,則P*也是正則表達式
⑤P和Q都是正則表達式,則P|Q是正則表達式
舉個栗子:
010101101*
(11|0*)*
以上都是都是正則表達式
|S|<=100
解題思路:令dp[i][j]表示第i個字符到第j個字符可否組成正則表達式,分5種狀況進行轉移就能夠了。
四:對拍方法:
對拍:你給兩個程序,和一個隨機數據生成器,而後系統去用這個隨機數據生成器的輸出做爲你這兩個程序的輸入,而後比較你這兩個程序的輸出,能夠找到一組使這兩個程序輸出不同的數據(若是存在的話),這樣就能夠提升正確率。
通常的對拍是:對於一個題,咱們寫一個暴力算法,管它什麼時間空間效率呢?!而後寫一個你對於這題的正確解法程序,拿這兩個程序進行對拍。
對拍的實現過程:
首先,在本地新建一個文件夾。
而後,在裏面放入std.exe、mkdt.exe、a.exe這三個exe程序(名字能夠本身亂取)。
std.exe:你暴力寫的一個作法或者你從網上找的一份AC代碼生成的程序,反正結果確定是對的。
a.exe:你的代碼生成的程序,你不知道他對不對或者你知道他是WA的可是你不知道哪裏WA了。
mkdt.exe:就是你的隨機數據生成器,你能夠用它去生成你認爲的合法數據。
最後,在文件夾中新建一個txt記事本文檔,在裏面輸入如下代碼:
1 :loop 2 mkdt.exe 3 std.exe 4 a.exe 5 fc std.out a.out 6 if %errorlevel%==0 goto loop 7 pause
而後保存,把".txt"這個後綴名改成".bat",雙擊運行便可!
5、隨機算法:
①隨機生成一棵樹:
1 for(int i=2;i<=n;i++)/*隨機生成一棵樹*/ 2 { 3 cout<<rand()%(i-1)+1<<' '<<i<<endl; 4 }//深度爲lgn
②隨機生成一棵長毛的鏈:
1 /*隨機生成一棵長毛的鏈:1~n/2*/ 2 for(int i=2;i<=n/2;i++) cout<<i-1<<' 'i<<endl; 3 for(int i=n/2+1;i<=n;i++) cout<<rand()%(i-1)+1<<' '<<endl;
③給你一張圖,生成一張圖:
1 /*給你一張圖,生成一張圖,10萬個點,20萬條邊 */ 2 map<long long,int> mp; 3 for(int i=1;i<=200000;i++) 4 { 5 int A=rand()%n+1; 6 int B=rand()%n+1; 7 while(A==B||mp[1ll*A*100005+B]) 8 { 9 A=rand()%n+1; 10 B=rand()%n+1; 11 } 12 mp[1ll*A*10005+B]=1; 13 cout<<A<<' '<<B<<endl; 14 }
④隨機生成一個連通圖:
先生成一棵樹,這棵樹上的邊是必定存在的,在隨機其餘的邊。
最近發現一些網站盜用個人blog,這實在不能忍(™把關於個人名字什麼的所有刪去只保留文本啥意思。。)!!但願各位轉載引用時請註明出處,謝謝配合噢~