文章目錄
- Description:
- Input:
- Output:
- Sample Input:
- Sample Output:
- Source:
Time Limit:1000MS Memory Limit:32768KDescription:
一個01串,我們可以對相鄰兩個字元實行交換位置的操作. 求最少的操作次數使得所有的1的位置連續. eg. s="010110",swap(s[1],s[2])之後,變成"001110". 所以答案是1.
Input:
多組資料,每組資料一個01字串.串長不超過10^5
Output:
首先輸出Case #k: ,然後是答案.
Sample Input:
01011010101100000
Sample Output:
Case #1: 1Case #2: 3Case #3: 0
Source:
dd
Status Submit
題目連結:http://acm.zjut.edu.cn/ShowProblem.aspx?ShowID=1703
---------------------分割線--------------------------
/* -----------------------------------------------
這一題還可以用回溯法來做,這是我第一次的解法。因為要回溯求出所有解,然後再求出其中一個最優解,
所以時間複雜度相當高,為指數時間O(2^(n-1)),結果當然就是Time Limit Exceed。
分析:
例如01串:01001110010001,也是先找出所有連續的1字串,1,111,1,1。不過這一個解法和之前的有所不同。
大概想法是這樣:對於兩個連續的1字串來說,他們合并的方式有兩種,1種就是左邊1字串向右移動,1種就是右邊
的字串向左移動。這裡的移動必然會產生不同的代價,除了特殊情況以外。例如,對於,,,100111..,左邊向右合并
會產生2步的代價,合并後會變成...1111...,而右邊向左邊移動則會產生6步的代價,合并後會變成...111100..
這樣一看,當然是前者用的步數少,其實不然。合并之後還要繼續和第三組1合并,然而根據和第三組合并的代價來看,
前者未必是最優的。所以這裡就有一個涉及到動態決策的問題,是向左合并還是向右合并呢?
對於這種問題,用回溯法搜尋出所有解然後求出最優解是可行的,這裡向左向右的決策將會產生一棵葉子數為2^n-1
的完全二叉樹,要搜尋出所有解就一定要搜尋到葉子,有2^(n-1)的分支,所以時間複雜為O(2^(n-1)),效率相當的
低。
此解法還有一些後續問題。。。。見代碼之後
----------------------------------------------- */
#include<stdio.h>char szStr[100005] ;int FindMinStep(int nOneSum, int nZeroSum , int nIndex) ;int main(void){int fFindOne = 0 ;int fFindZero = 0 ;int nMinToLeft = 0 ;int nMinToRight = 0 ;int nMinStep = 0 ;int nOneFwSum = 0 ;//0前面的1,比如110001,指的是11int nOneBhSum = 0 ;//0後面的1,比如11000111,指的是111int nZeroSum = 0 ;int i = 0 ;int j = 1 ; while(scanf("%s",szStr) != EOF){fFindOne = fFindZero = -1 ;nMinToLeft = nMinToRight = nMinStep = 0 ;nOneFwSum = nOneBhSum = nZeroSum = 0 ;i = 0 ;while(szStr[i] != '\0'){if('1' == szStr[i]){fFindOne = 1 ;if(fFindZero > 0){nOneBhSum++ ;if('0' == szStr[i+1] || '\0' == szStr[i+1]){nMinToLeft = nOneBhSum*nZeroSum + FindMinStep(nOneFwSum+nOneBhSum,nZeroSum,i+1) ;//向左決策nMinToRight = nOneFwSum*nZeroSum + FindMinStep(nOneFwSum+nOneBhSum,0,i+1) ;//向右決策nMinStep = nMinToLeft < nMinToRight ? nMinToLeft : nMinToRight ;//左右分支中最少步數break ;}}else{nOneFwSum++ ;}}else if('0' == szStr[i]){if(fFindOne > 0 ){nZeroSum++ ;fFindZero = 1 ; }}i++ ;}printf("Case #%d: %d\n",j,nMinStep) ;j++ ;} return 0 ;}//參數的意義分別是:此時已經合并1的數目、與下一個1串相隔多少個0、此時在01串的下標int FindMinStep(int nOneSum, int nZeroSum , int nIndex){int i = nIndex ;int fFindZero = 1 ;int nMinToLeft = 0 ;int nMinToRight = 0 ;int nMinStep = 0 ;int nOneFwSum = nOneSum ; int nOneBhSum = 0 ;int nZeroThisSum = nZeroSum ;if('\0' == szStr[nIndex]){return 0 ;}else{while(szStr[i] != '\0'){if('1' == szStr[i]){nOneBhSum++ ;if('0' == szStr[i+1] || '\0' == szStr[i+1]){nMinToLeft = nOneBhSum*nZeroThisSum + FindMinStep(nOneFwSum+nOneBhSum,nZeroThisSum,i+1) ;//繼續向左決策nMinToRight = nOneFwSum*nZeroThisSum + FindMinStep(nOneFwSum+nOneBhSum,0,i+1) ;//繼續向右決策nMinStep = nMinToLeft < nMinToRight ? nMinToLeft : nMinToRight ;return nMinStep ;//回溯}}else if('0' == szStr[i]){nZeroThisSum++ ;}i++ ;}}return nMinStep ;//回溯}
/* -------------------------------------------
回溯法解法後續問題:
分析:
那個回溯法遍曆產生完全二叉樹的所有分支其實有一半是和所有1串全部向右移的情況一樣的,這裡指的
一樣,並不是指步數一樣,而是指1串的最終停留的位置一樣。產生這種情況的前提是,我的演算法是從左
往右遍曆的,因為演算法是從01串的左往右進行合并,所以最後一個決策肯定是針對已經合并的1串和最右
邊也就是最後一個1串的合并。如果最後一個決策是已經合并串的向右移動,那就是說所有1串都往最右邊
靠了。否則則是最後一個1串往左邊合并。
例如001110111001,所有1串向右移的情況有兩種,第一種移法就是所有1串直接向右移,第二種做法就
是中間的1串先向左,再向右。這兩種移法最終結果都一樣,但是步數卻不一樣,前者肯定是向右時產
生最少步數的,而後者先向左再向右這一種移法,中間的1串重複移動了,所以結果無論相對於全部向右移,
或者其它移動方案來說都不是最優。
現在回到剛才提到遍曆產生的完全二X樹,所有包含右葉子的那些分支都和全部1串向右移產生的情況一
樣(在紙上畫畫就可以看出來了),然而只有所有決策都是向右移的情況這是最優的。所以這些分支都是
無用且浪費時間的。
這裡可以引申出一個結論:
在這一棵遍曆二X樹中,如果一開始的決策是向右的,在後面的決策中,只允許產生一次變向決策的(也就是
變向左移動)。這一個變向的決策,其實就是找到了線性解法中的固定點,這是一個轉折點。在這個轉折點之
後所有的1串都要向左移動。如果在變向之後,在後續的決策中還要再產生一個變向決策(向右決策),則證明
之前做的一個變向決策所找到的固定點不會產生最優解,這個最優解必然在最後一個的變向決策中。
所以,對於回溯的最佳化可以在這裡下手。
大概如下:
1、先記下一路向左移動所產生的步數。
2、從第一個向右移動開始決策,記錄步數。然後在遇到第一個變向決策時,設定已經變向的標誌位。
3、然後繼續一邊記錄向左決策產生的步數,一邊記錄一直向右決策產生的步數(產生第二次變向決策時有用)。
4、如果在後續決策中沒有產生第二次變向,則這一個就是最優解。
5、如果在後續決策中產生了第二次變向,剛跳到從這個變向點開始遍曆,而第二步儲存的一路向右產生的步數
就變成了假設在決策到這一個變向點之前所產生的最少步數。
6、然後繼續決策,如果遇到變向決策,則回到了第2、3步,如果此後沒有再遇到變向決策,則這一分支其實就是
所有1串一路向右移動的情況。
思路大概就是這樣,其實就是減少了無用分支,取消了回溯這兩步。
這樣就可以將回溯法變為線性演算法o(n),n為1串的數目。
代碼就等考完試再實現~~~~
------------------------------------------- */
-----------------------分割線---------------------
/* ---------------------------------------------------------------------------------------------
分析:
1、最少步數將所有1的位置連續,這應該是個最佳化問題。嗯,先想到的是用動態規劃法。但是......我
寫不出來....現在還在糾結能不能用動態規劃.......狀態位移方程也寫不出來。
2、首先要找出01串裡面的所有連續的1字串,例如01串:01001110010001,這裡面的所有連續的1字串有,
1,111,1,1,總共有4連續的1字串。然後我是這樣想的(錯了N次之後),要做到最少步數,就必須做到最
少次數去移動那些連續1字串或者最少次數去移動那些連續的0。因為題目是針對1,所以我選擇去移動
連續的1字串。
對於串01001110010001,這裡總共有4個連續1字串,而無論怎樣去移動,程式的最終結果都是所有的1
放在一起。所以,對於4個連續1字串來說,只要移動其中的3個字串就可以了。同理對於N個連續的1字串
,只需移動其中的N-1個的字串就可以做到最少步數。
3、所以整體的思路應該是這樣:
1、先找出所有連續的1字串,然後每一次只固定一個連續的1字串,其餘的1字串則向這個固定的中心移動,
統計步數,然後比較移向不同中心時所產生的步數,求出最少的步數。
這種演算法的時間複雜為O(n^2),n為1字串的數目。因為這種漸近平方時間,所以Time Limit Exceed。
--------------------------------------------------------------------------------------------- */
#include<stdio.h>#include<memory.h>char szStr[100005] ;int nOneGroup[55000] ;//記錄連續1字串各有多少個1int nZeroGroup[55000] ;//記錄1字串之前0字串有多少個0int main(void){int i = 0 ;int k = 0 ;int j = 1 ;int fFindOne = 0 ;int nZeroIndex = -1 ; int nOneIndex = 0 ; int nMinStep = 0 ;int nTempStep = 0 ;int nTempZero = 0 ;freopen("in.txt","r",stdin) ;while(scanf("%s",szStr) != EOF){i = k = 0 ;nZeroIndex = -1 ;nOneIndex = 0 ; fFindOne = 0 ;nMinStep = nTempStep = nTempZero = 0 ;memset(nOneGroup,0,sizeof(nOneGroup)) ;memset(nZeroGroup,0,sizeof(nZeroGroup)) ;while(szStr[i] != '\0'){if('1' == szStr[i]){if(0 == fFindOne){fFindOne = 1 ;nZeroIndex++ ;}nOneGroup[nOneIndex]++ ;}else if(1 == fFindOne){nZeroGroup[nZeroIndex]++ ;if('1' == szStr[i+1]){fFindOne = 0 ;nOneIndex++ ;}}i++ ;}for(i = 0 ; i <= nOneIndex ; ++i){nTempStep = 0 ;nTempZero = 0 ;for(k = i ; k > 0 ; --k)//左邊的1字串向固定1字串移動{nTempZero += nZeroGroup[k-1] ;nTempStep += nOneGroup[k-1]*nTempZero ;}nTempZero = 0 ;for(k = i ; k < nOneIndex ; ++k)//右邊的1字串向固定1字串移動{nTempZero += nZeroGroup[k] ;nTempStep += nOneGroup[k+1]*nTempZero ;}if(0 == i){nMinStep = nTempStep ;}else if(nTempStep < nMinStep){nMinStep = nTempStep ;}}printf("Case #%d: %d\n",j,nMinStep) ;j++ ;}return 0 ;}
/* -----------------------------------------------
在O(n^2)基礎上,減去兩個memset調用,降低一下常數因子...結果還是Time Limit Exceed
----------------------------------------------- */
#include<stdio.h>char szStr[100005] ;//搞錯了,原來是要10萬個....int nOneGroup[55000] ;int nZeroGroup[55000] ;int main(void){long i = 0 ;long k = 0 ;long j = 1 ;long fFindOne = 0 ;long nZeroIndex = -1 ; long nOneIndex = 0 ; long nMinStep = 0 ;long nTempStep = 0 ;long nTempZero = 0 ;while(scanf("%s",szStr) != EOF){i = k = 0 ;nZeroIndex = -1 ;nOneIndex = 0 ; fFindOne = 0 ;nMinStep = nTempStep = nTempZero = 0 ;while(szStr[i] != '\0'){if('1' == szStr[i]){if(0 == fFindOne){fFindOne = 1 ;nZeroIndex++ ;}nOneGroup[nOneIndex]++ ;}else if(1 == fFindOne){nZeroGroup[nZeroIndex]++ ;if('1' == szStr[i+1]){fFindOne = 0 ;nOneIndex++ ;}}i++ ;}for(i = 0 ; i <= nOneIndex ; ++i){nTempStep = 0 ;nTempZero = 0 ;for(k = i ; k > 0 ; --k){nTempZero += nZeroGroup[k-1] ;nTempStep += nOneGroup[k-1]*nTempZero ;if(i == nOneIndex){nZeroGroup[k-1] = nOneGroup[k-1] = 0 ;}}nTempZero = 0 ;for(k = i ; k < nOneIndex ; ++k){nTempZero += nZeroGroup[k] ;nTempStep += nOneGroup[k+1]*nTempZero ;}if(0 == i){nMinStep = nTempStep ;}else if(nTempStep < nMinStep){nMinStep = nTempStep ;}if(i == nOneIndex){nOneGroup[nOneIndex] = 0 ;nZeroGroup[nOneIndex]= 0 ;}}printf("Case #%d: %d\n",j,nMinStep) ;j++ ;}return 0 ;}
-------------------分割線-----------------------------
/* ------------------------------------------------
分析:
其實這種演算法就是在平方演算法的基礎上做最佳化。
因為計算移向每一個固定中心1串的步數時,計算過程中所得到的部分結果也包含了計算移向下一個固定中心1
串的步數時所需要的資料,所以在計算當前步數時可以將這些結果儲存起來,方便下一次計算,以做到線性
時間複雜度。
舉個例子:
01串:011000111001111001
第一個固定中心1串是最左邊的1串,其餘所有1串向它靠近時,總共產生的步數有36步,也就是nAllToLeftStep
的值。
然後固定第二個1串時,相對於固定第一個1串時來說,右邊的1串(包含第二個1串)向左移動的步數將會有所
下降,下降的數目就是第一個1串和第二個1串之前0的數目(nLeftZeroSum)乘以右邊1串的數目(nRightOneSum),
也就是nDecreStep的值。這裡解釋一下,因為固定了第二個1串,所以原先本來要移向第1個1串的就只需要移動
到第二個1串則可,而第二個1串則不動,所以相對於nAllToLeftStep,固定第二個1串,移動少了nDecreStep步
數。因為固定中心向右移了,所以在固定中心左邊的1串向他靠近的時候也會增加新的步數,也就是nIncreStep。
這裡也需要儲存下一次計算1串向右移的資料,也就是左邊1串的數目(nLeftOneSum)乘以固定中心和前一個固定
中心之前0的數目(nZeroGroup[i-1])。
所以,總的來說就是線性時間,時間複雜度 O(n)。
------------------------------------------------ */
#include<stdio.h>#include<memory.h>char szStr[100005] ;int nOneGroup[55000] ;int nZeroGroup[55000] ;int main(void){long i = 0 ;long k = 0 ;long j = 1 ;long fFindOne = 0 ;long nZeroIndex = -1 ; long nOneIndex = 0 ; long nMinStep = 0 ;long nTempStep = 0 ;long nTempZero = 0 ;long fCal = 0 ;long nAllToLeftStep = 0 ;long nRightOneSum = 0 ;long nLeftZeroSum = 0 ;long nLeftOneSum = 0 ;long nDecreStep = 0 ;long nIncreStep = 0 ;while(scanf("%s",szStr) != EOF){i = k = 0 ;nZeroIndex = -1 ;nOneIndex = 0 ; fFindOne = fCal = 0 ;nMinStep = nTempStep = nTempZero = nAllToLeftStep = nRightOneSum = 0 ;nLeftZeroSum = nLeftOneSum = nDecreStep = nIncreStep = 0 ;memset(nOneGroup,0,sizeof(nOneGroup)) ;memset(nZeroGroup,0,sizeof(nZeroGroup)) ;while(szStr[i] != '\0'){if('1' == szStr[i]){if(0 == fFindOne){fFindOne = 1 ;nZeroIndex++ ;}if('0' == szStr[i+1] || '\0' == szStr[i+1]){fCal = 1 ;}nOneGroup[nOneIndex]++ ;if(nOneIndex >= 1 && 1 == fCal){nTempZero += nZeroGroup[nOneIndex-1] ;nTempStep += nTempZero*nOneGroup[nOneIndex] ;nRightOneSum += nOneGroup[nOneIndex] ;}fCal = 0 ;}else if(1 == fFindOne){nZeroGroup[nZeroIndex]++ ;if('1' == szStr[i+1]){fFindOne = 0 ;nOneIndex++ ;}}i++ ;}nMinStep = nTempStep ;nAllToLeftStep = nTempStep ;for(i = 1 ; i <= nOneIndex ; ++i){nTempStep = 0 ;nLeftZeroSum = nZeroGroup[i-1] ;nDecreStep += nRightOneSum * nLeftZeroSum ;nLeftOneSum += nOneGroup[i-1] ;nIncreStep += nLeftOneSum * nZeroGroup[i-1] ;nTempStep = nAllToLeftStep - nDecreStep + nIncreStep ;nRightOneSum -= nOneGroup[i] ; if(nTempStep < nMinStep){nMinStep = nTempStep ;}}printf("Case #%d: %d\n",j,nMinStep) ;j++ ;}return 0 ;}
/* --------------------------------------------------------------------------------
少了兩次memset調用,降低了常數因子,12MS
-------------------------------------------------------------------------------- */
#include<stdio.h>char szStr[100005] ;int nOneGroup[55000] ;int nZeroGroup[55000] ;int main(void){long i = 0 ;long k = 0 ;long j = 1 ;long fFindOne = 0 ;long nZeroIndex = -1 ; long nOneIndex = 0 ; long nMinStep = 0 ;long nTempStep = 0 ;long nTempZero = 0 ;long fCal = 0 ;long nAllToLeftStep = 0 ;long nRightOneSum = 0 ;long nLeftZeroSum = 0 ;long nLeftOneSum = 0 ;long nDecreStep = 0 ;long nIncreStep = 0 ;while(scanf("%s",szStr) != EOF){i = k = 0 ;nZeroIndex = -1 ;nOneIndex = 0 ; fFindOne = fCal = 0 ;nMinStep = nTempStep = nTempZero = nAllToLeftStep = nRightOneSum = 0 ;nLeftZeroSum = nLeftOneSum = nDecreStep = nIncreStep = 0 ;while(szStr[i] != '\0'){if('1' == szStr[i]){if(0 == fFindOne){fFindOne = 1 ;nZeroIndex++ ;}if('0' == szStr[i+1] || '\0' == szStr[i+1]){fCal = 1 ;}nOneGroup[nOneIndex]++ ;if(nOneIndex >= 1 && 1 == fCal){nTempZero += nZeroGroup[nOneIndex-1] ;nTempStep += nTempZero*nOneGroup[nOneIndex] ;nRightOneSum += nOneGroup[nOneIndex] ;}fCal = 0 ;}else if(1 == fFindOne){nZeroGroup[nZeroIndex]++ ;if('1' == szStr[i+1]){fFindOne = 0 ;nOneIndex++ ;}}i++ ;}nMinStep = nTempStep ;nAllToLeftStep = nTempStep ;for(i = 1 ; i <= nOneIndex ; ++i){nTempStep = 0 ;nLeftZeroSum = nZeroGroup[i-1] ;nDecreStep += nRightOneSum * nLeftZeroSum ;nLeftOneSum += nOneGroup[i-1] ;nIncreStep += nLeftOneSum * nZeroGroup[i-1] ;nTempStep = nAllToLeftStep - nDecreStep + nIncreStep ;nRightOneSum -= nOneGroup[i] ; if(nTempStep < nMinStep){nMinStep = nTempStep ;}nZeroGroup[i-1] = nOneGroup[i-1] = 0 ;if(nOneIndex == i){nOneGroup[i] = 0 ;nZeroGroup[i-1] = nZeroGroup[i] = 0 ;}}if(0 == nOneIndex){nZeroGroup[0] = nOneGroup[0] = 0 ;}printf("Case #%d: %d\n",j,nMinStep) ;j++ ;}return 0 ;}
-------------------分割線-----------------------------
/* ------------------------------------------------
最後就是想問一下一開始想到的動態規劃法。
不知道對於這一道題來說,動態規劃法是否可行?動態規劃的可行條件為:最優子結構和無後效性,這兩個
我都沒有找到但是又覺得動態規劃可行。因為上一種回溯法中已經涉及到動態決策問題,而動態規劃可以用
在這些地方,這裡的向左向右合并就涉及到了決策的問題。
動態規劃法的核心是狀態和狀態轉移方程,對於這一道題來說,狀態應該是此時左邊字串聯續1的個數,而狀
太轉移方程應該就是向左向右移動產生的代價等等的方程(好吧,我不會寫 = =)...
所以求指教.................
------------------------------------------------
-------------------分割線-----------------------------
附icelights同學的解法,一種完全不同的思路,很不錯:http://blog.csdn.net/icelights/article/details/7713058
---------------------------------------------------