日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

最大字段和 冲出暴力枚举

發布時間:2025/6/15 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 最大字段和 冲出暴力枚举 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這篇解題報告是對我最近一些題的總結,里面的代碼都是我解題,優化,再優化的過程的記錄,記錄了自己對算法的完善與優化思路,還有對編程哲學的理解:do it,do it well。

很感謝孫老師您,讓自己可以找到利用計算機作比只是單純玩游戲更有意義又更有趣的事情,又通過您的課,通過照著您給的路自己不斷探索,感覺自身能力得到了很大的提升,從當時一個小程序漏洞百出,思路拙劣,對oj系統的不熟悉,甚至當時上課時平時的練習和考試都讓自己感覺不盡人意,到現在自己不斷地追求完善代碼完善思路,追求完美追求最優,雖然還是拙劣但是感覺自己在努力,感覺在自己現有的知識程度上已經做的很美了,這種在心里涌上的孜孜不倦的追求的感覺讓自己感覺到了人生的意義,正如歌德在《浮士德》里所表達的主題一樣,完善的境界永遠不可及,人類所能達到的最高成就,恰在于一種自強不息的創造性生活本身,一種不斷進步的道路與過程本身。

好了,說了那么多,該切入正題了,審視自身的努力,反思自己的不足,就可以很清楚的一個菜鳥標準的特征是:耗時費力的暴力枚舉浪費了大量資源,永遠不變的單一的選擇排序,不自覺暴露出的goto使用……所以我今天借三個同樣是關于求出最優解的問題,來說明我是怎么沖出暴力枚舉。

第一個問題,很簡單,是ustcoj上的1389,連續子數組的和,題目大意如下:輸入一個整型數組,數組里有正數也有負數。數組中連續的一個或多個整數組成一個子數組,每個子數組都有一個和。求所有子數組的和的最大值。(數組里的元素可正可負)

想都不用想,直接暴力枚舉,太容易了,且只是個o(n^2)的算法,由于n小于5000,必然不用當心超時,直接用暴力枚舉就可以過了,看代碼:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <stdio.h> #define max 5000 int a[max+1]; int qiuzhi(int n)???????????????? //枚舉所有的情況,返回里面的最大值 { ????????int i,j,ans,temp; ????????ans=a[0]; ????????for(i=0;i<n;i++) ????????{ ?????????????????temp=a[i]; ?????????????????if(temp>ans) ???????????????????????????????????ans=temp; ?????????????????for(j=i+1;j<n;j++) ?????????????????{ ??????????????????????????temp+=a[j]; ??????????????????????????if(temp>ans) ???????????????????????????????????ans=temp; ?????????????????} ????????} ????????return ans; } int main() { ????????int n,i,answer; ????????while(scanf("%d",&n)) ????????{ ?????????????????if(n==0) ??????????????????????????break; ?????????????????for(i=0;i<n;i++) ??????????????????????????scanf("%d",&a[i]); ?????????????????answer=qiuzhi(n); ?????????????????printf("%d\n",answer); ????????} ????????return 0; }

解釋都不必了,就是把所有的子序列的和枚舉一遍求出它的最大值,看不懂的話就好好回頭學好基本功。你可以寫出這些代碼就停了,畢竟accept萬歲!深有體會,一個accept不容易啊,但你就這樣放著它不管了,你就永遠只會到這個水平,永遠得不到提升,看看有些問題的排行榜,看看上面的大神們,有時幾乎從第一名到第十名都是同一個大神,足以看出他們不滿足于僅僅的accept,也足以理解他們為什么有那么強的能力。永遠記住有完成,就可以更好的完成,有優就有更優。永遠相信當前的方案不是最好的,為求完善孜孜不倦!

回過頭來,仔細想想,對于這樣一個問題,o(n^2)的時間復雜度是不是太大了,是不是可以繼續縮短它運行時和所耗的空間。我們要達成這樣的目的,仔細來看要,無非可以從兩方面入手,一是先通過特殊處理來減少枚舉的復雜度;二是減少枚舉的次數。這些也是我對做過的這些題目優化過程的總結。要完成這兩個方面,一即通過一些搜索、排序等增加有序度的運算來將難以辦到的枚舉變得簡單;二即從問題條件入手通過推理找到一些隱性的條件,增加這些隱性的條件來減少枚舉次數。我們來看這個問題,它說要找到所有子數組的和的最大值,這個最大值滿足些什么特征呢?或者說這個和最大的子數組滿足些什么特征呢?至少我們可以找到這一條特征,這個子數組里的第一個數必然不可能小于零,為方便我們不妨稱之為定理一。定理一很好理解,用反證法,如果這個子數組的第一個數小于零了,去掉它的第一個數所得的子數組必然比它要大,這與它的和最大相矛盾,所以定理一成立。我們根據這個條件可以在枚舉加入此條件,減少枚舉次數:

for(i=0;i<n;i++)

{

?? if(a[i]<0)

????? continue;

?? ……

}

雖然這樣算法的時間變的不穩定,我不會求它的時間復雜度了,但可以肯定對于大多數時候比之前要快很多了,我們繼續對它進行優化,也就是我們繼續尋找隱性條件,其實對于定理一我們可以進行這樣的推廣:這個子數組里的從第1項加到第n項的和必不小于0,即該子數組的前n項的和必大于0。稱為定理二,證明不必了,跟定理一一樣。我們繼續增加這個條件,減少枚舉的次數:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 for(i=0;i<n;i++) { ???if(a[i]<0) ??????continue; ????temp=a[i]; ?????????????????if(temp>ans) ???????????????????????????????????ans=temp; ?????????????????for(j=i+1;j<n;j++) ?????????????????{ ??????????????????????????temp+=a[j]; ????????????????if(temp<0) ????????????????????{ ????????????????????????i=j; ????????????????????????continue; ????????????????????} ??????????????????????????if(temp>ans) ???????????????????????????????????ans=temp; ?????????????????} }

代碼運算速度繼續得到加快,特別是當temp<0時,i=j,直接跳過的不必要枚舉又有了很多,我們繼續想想還可以繼續優化嗎?還有隱式條件嗎?我們繼續分析,不難得到這點,即我們得到最大值的那個數列必然滿足這點:在滿足定理二的條件下(滿足定理二必滿足定理一),盡可能多的數相加得到的值最大,這個顯而易見的,對于特定的數組,它的每個數都大于0的話,最大值子數列即它本身,即滿足當前條件下盡可能多的數相加得到的值最大,這個隱式條件有什么用呢?根據它實際我們可知不必枚舉所有的子數組的值,我們只需枚舉可以去到最多的滿足條件的數的數組就可以求出最大值,根據這個我們只需這樣進行一次枚舉即可:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int qiuzhi(int n) { ?????????int i,ans,temp=0; ?????????ans=a[0]; ?????????for(i=0;i<n;i++) ?????????{ ?????????????????temp+=a[i]; ?????????????????if(temp>ans) ??????????????????????????ans=temp; ?????????????????if(temp<0) ??????????????????????????temp=0; ?????????} ?????????return ans; }

于是乎,我們的算法的時間復雜度從o(n^2)降到了o(n),優化優化再優化,沖破單一暴力枚舉,你的能力得到提升。到了這樣速度已經很好了,但是不要滿足不要逗留,就像浮士德的對逝去的瞬間發出的那句“逗留一下吧,你是那樣美!”,之后等待他的就是萬劫不復的地獄。孜孜不倦的渴求,勇往直前的奮進,才能讓人永遠激昂,永遠感受到青春的力量,永遠立于不敗!

這樣之后,我們實際上還可以進行優化,精益求精,因為定理一和定理二可以這樣推廣:對于第一個數開始,和最后一個數開始同樣都是滿足的,所以掃描可以從兩頭同時開始,這樣我們在部分情況下可以節省部分時間,但是這么做造成了雙倍的空間,和幾個多出來的對中部的判斷情況進行判斷,這樣又造成了額外的開支,使這種優化變得不必要。我嘗試了幾次對這個思路進行特殊處理后來繼續優化,但都失敗了,就不好意思把不成熟的代碼拿出來。

第二個問題,是ustcoj上的1353切繩子,題目大意如下:開始時你手上有一根長為正整數N的繩子。你選擇一個長度X(1 <= X <= N-1且為整數),將繩子切成長為XN-X兩部分,得到操作分(-X^2+N*X+K)。之后,你要在切出來的兩段繩子中選擇一段再做同樣的操作得到三段繩子。繼續下去,直到你得到N段長為1的繩子為止(這時你無法再切下去了)。最終得分為你操作分的總和。如果每次切繩子時長度X為隨機選擇的(等概率),手里有多段繩子時切的繩子也是從可以繼續切的繩子(長度大于1)中隨機選擇的,那么最終得分的期望值是多少?(輸出結果只要整數部分)

初始解法,遞歸法暴力枚舉,解釋詳見注釋:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 #include <stdio.h> float a[1001]={0};? //作為遞歸使用時加快遞歸算法速度的輔助儲存數組。 int b[1001]={0}; float f(int n)??? //計算當前n下所有情況的總分 { ?????????int i; ?????????if(n>1) ?????????{ ?????????if(a[n]) ?????????????????return a[n]; ?????????else ?????????{ ?????????for(i=1;i<n;i++) ?????????????????{ ??????????????????????????a[n]+=n*i-i*i;??? //題目中的表達式 ??????????????????????????a[n]+=f(n-i)+f(i); ?????????????????} ?????????} ?????????return a[n]; ?????????} ?????????else ?????????????????return 0; } int h(int n)? //遞歸計算n下的總可能情況數 { ?????????int i; ?????????if(n>2) ?????????{ ?????????if(b[n]) ?????????????????return b[n]; ?????????else ?????????{ ?????????????????for(i=1;i<n;i++) ??????????????????????????b[n]+=h(n-i)+h(i); ?????????????????return b[n]; ?????????} ?????????} ?????????else ?????????{ ?????????????????if(n==1) ?????????????????????return b[1]=0; ?????????????????else ??????????????????????????return b[2]=1; ?????????} } int main() { ?????????int n,k,s; ?????????float g; ?????????while(~scanf("%d %d",&n,&k)) ?????????{ ?????????????????s=h(n);?? //得到總的可能情況數 ?????????????????g=f(n)/s+(n-1)*k;? //計算數學期望 ?????????????????printf("%.0f\n",g); ?????????} ?????????return 0; }

暴力枚舉沒任何技術含量,時間被大量的浪費,且計算的過程會出現越界的情況,該代碼最多可以準確的算到n=60左右,再大計算過程中a[n]里存儲的值超出float范圍,到后面long double也裝不下。于是繼續進行分析,比如說,對于求n下的總情況與n之間的函數關系是否可以求出來呢?這實際上就看你的高中的數列功底如何了,這兩個數學表述出來都是數列求通項的問題。

求總的情況數:記為b數列,記第n項為bn,記前n項的和為Sn,求滿足以下條件的數列,b1=0,b2=1,

bn=(bn-1+b1)+(bn-2+b2)+…+(b2+bn-2)+ (b1+bn-1)??? ……?eq \o\ac(,1)1

求通項:

bn=2Sn-1?????? ……?eq \o\ac(,2)2

又因:bn=Sn-Sn-1? ……?eq \o\ac(,3)3

?eq \o\ac(,2)2?eq \o\ac(,3)3可得:Sn=3Sn-1為一等比數列。又S1=0,S2=1;所以有Sn=3n-3

代入?eq \o\ac(,2)2有:bn=2Sn-1=2*3n-3

對于求總的得分數有:同樣記為a數列,第n項為an,記前n項的和為Sn,求滿足以下條件的數列,a1=0,a2=1,

an=(an-1+a1+n*1-12)+(an-2+a2+n*2-22)+…+(a2+an-2+n*n-2-(n-2)2)+ (a1+an-1+n*n-1-(n-1)2)??? ……eq \o\ac(,4)4

化簡得到:Sn=3Sn-1+n*n+1*(n-1)/6?(可以左右兩邊同時加一個減一個,化成形如Sn+f(n)=3(Sn-1+f(n-1))這樣,化成一個等比數列了再求解,求出來后通式很復雜,不寫出來了),這個式子的通項很復雜,可以用左右兩邊加一個減一個方法化成等比數列,不過就算是求出來了,表達式過于恐怖了,不好寫,于是求到這步就停止了,繼續用遞歸往下求就行了,雖然因為數學不夠好求不下去了,但比之開始又快了好多,代碼如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <math.h> #include <stdio.h> double a[1001]={0}; double f(int n) { ?????????if(a[n]) ??????????????????return a[n]; ?????????else ?????????{ ?????????if(n>2) ?????????{ ??????????????????return a[n]=n*(n*n-1)/6+3*f(n-1); ?????????} ?????????else ??????????????????return 1; ?????????} } int main() { ?????????int n,k; ?????????double g; ?????????while(~scanf("%d %d",&n,&k)) ?????????{ ??????????????????if(n>2) ??????????????????{ ?????????????????????g=(f(n)-f(n-1))/(2*pow(3.0,n-3))+(n-1)*k; ??????????????????} ??????????????????else ??????????????????{ ??????????????????????????if(n==1) ???????????????????????????????????printf("0\n"); ??????????????????????????else ???????????????????????????????????g=k+1; ??????????????????} ??????????????????printf("%.0lf\n",g); ?????????} ?????????return 0; }

比開始時代碼都精簡了好多。

繼續代入,由于bn=2*3n-3,實際算出了求出來大概an3n-3項系數大概在10*3n-3左右,其他部分在n>=5an/bn都是小數,由于只要求輸出整數部分,所以在n>=5時可以取an/bn=5即可,對n<5時單獨列表繼續進行簡化;又g=an/bn+(n-1)*k=n*k-k+5。

有新的簡化:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <stdio.h> int main() { ?????????int n,k; ?????????long long g; ?????????while(~scanf("%d %lld",&n,&k)) ?????????{ ??????????????????if(n>=5) ??????????????????????????g=n*k+5-k; ??????????????????else ??????????????????{ ??????????????????????????switch(n) ??????????????????????????{ ???????????????????????????????????case 0: ???????????????????????????????????case 1: ????????????????????????????????????????????g=0; ????????????????????????????????????????????break; ???????????????????????????????????case 2: ????????????????????????????????????????????g=k+1; ????????????????????????????????????????????break; ???????????????????????????????????case 3: ????????????????????????????????????????????g=2*k+3; ????????????????????????????????????????????break; ???????????????????????????????????case 4: ????????????????????????????????????????????g=3*k+4; ????????????????????????????????????????????break; ??????????????????????????} ??????????????????} ??????????????????printf("%lld\n",g); ?????????} ?????????return 0; }

現在算法直接變得與n無關了,也突破了暴力枚舉,沒辦法,要命的數學,數學使用好了,什么都能迎刃而解。上題是邏輯的突破,現在這題是數學,是運算方法的突破。

第三題,是ustcoj上的1274k_star風波,題意表達成數學語言就是:把一個長度為n的數組分為m(m<n),保證連續且不改變順序,使得這m個子數列和中的最大值在所有分法里面最小,輸出最小值,和該分法(方便這篇文章的抒寫,省去這個要求)。數組里的元素全為正整數。

現在我只分析輸出最小值,實際加一個數組記錄分法就可以輸出了。

首先還是暴力枚舉,鑒于我寫的代碼太長,很占篇幅,枚舉就不直接放出來了,關于枚舉的優化演變我在第一個問題里面假設最簡單的枚舉法,看這篇文章的人都會寫。但是可以肯定暴力枚舉必然通不過,因為是枚舉次數是一個組合數C(m,n),太占時間了。

于是我優化出了一個最長時間在o((n-m)*n),最短時間在o(n)的不穩定算法。一樣的尋找隱式條件,從可能最小值入手,一個個判斷是不是可以成立,成立了即終止。首先可能的最小值是在該數列的最大值和最大值加它旁邊的小值,假設是a[k],首先先用一個搜索算法這個搜索到這個a[k]的位置,然后判斷a[k+1]a[k-1]的大小(假設a[k-1]更小),最小值取得的范圍在a[k]a[k-1]之間,然后掃描一遍整個數組,看是否可以滿足這個范圍內取得最小值,如果不滿足最小值取得的范圍就在a[k]+a[k-1],比較a[k+1]a[k-2]的大小(假設a[k+1]小),那么范圍在a[k]+a[k-1]+a[k+1]之間了,繼續掃描一遍整個數組,看能不能取得,不能繼續進行以上步驟,一直進行n-m次,這部分代碼如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 ......//先用搜索算法找到數組最大值位置k temp=0,min=0,maxin=0,point=0,r=l=0,fenshu,zhongzhi;//? temp是記錄每個部分的,min記錄要輸出的值,maxin記錄取值范圍,point記錄a[k]這次是加還是減,r記錄加的值,l記錄減的值,fenshu記錄當前已經分出的份數,zhongzhi用于判斷是滿足最小值范圍內可以輸出,滿足則輸出最小值。 for(i=0;i<n-m;i++) { maxin=0; zhongzhi=1; ???if(point) ???{ ??????maxin+=a[k+r]; ??????????a[k+r]=0; ???} ???else ???{ ??????maxin+=a[k-l]; ???????????a[k-l]=0; ???} ???if(a[k+r+1]<a[k-l-1])??? //判斷a[k+r+1]和a[k-l-1],確定k運行的方向(是加還是減) ????{ ????????r++; ??????????point=1; ?????????} ???else ???{ ??????l++; ??????????point=0; ???} ???...... }

以上部分是掃描數組部分之前的代碼,for循環內的省略部分代碼如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 fenshu=1;???? //初始就有包含a[k]的那份,所以fenshu=1; ???min=maxin;? ???for(j=0;j<n;j++) ???{ ??????if(a[j]==0)????? //跳過包含a[k]的那份,因為開始時已計數。 ???????????{ ???????????????????????temp=0; ??????????????????fenshu++; ??????????????????j=k+r; ????????????????????????????continue; ???????????} ??????if(point)??????????????????? //判斷開始k是加一,還是減一 ???????????{ ?????????????if(temp+a[j]<=maxin+a[k+g+1]) ??????????????????{ ?????????????????????temp+=a[j]; ?????????????????????if(temp>min) ?????????????????????min=temp; ??????????????????} ??????????????????else ??????????????????{ ??????????????????????temp=a[j]; ??????????????????????fenshu++; ??????????????????} ???????????} ???????????else ???????????{ ???????????if(temp+a[j]<=maxin+a[k-l-1]) ??????????????????{ ?????????????????????temp+=a[j]; ?????????????????????if(temp>min) ?????????????????????min=temp; ??????????????????} ??????????????????else ??????????????????{ ??????????????????????temp=a[j]; ??????????????????????????fenshu++; ??????????????????} ???????????} ???????????if(fenshu>m) //要使得滿足最小值在范圍內,必需使分法數大于m則不成立退出 ???????????{ ??????????????zhongzhi=0; ???????????????????break; ???????????} ???} } ?if(zhongzhi)?? break;?? //fenshu<m的話即可以保證該使得最小值在范圍里的分法成立,即終止,不然增加范圍,開始下次循環。

以上部分即優化后的代碼主要部分,算法由單純的暴力枚舉變成有選擇性有條理的枚舉,讓一切井然有序,層層遞進,算法變得更優了。

思考解決方案,設計方案基本思路、解法、算法,用某種語言將頭腦里前兩步所構建的算法表述出來,然后以此為基礎,一步步完善,精益求精,以求盡善盡美,堅持不懈,就算路途中與千億次失敗相伴,成功也會最終慢慢光臨。雖然寫的代碼還是很不好漏洞很多,但是從完全沒有頭緒,到用很拙劣的思路寫出很拙劣的代碼,然后再到一步步的對它們企求精美,于是思路慢慢被打開,走向一片廣闊的天地,不再受困于在狹縫里挖掘,自己也就收獲了很多,收獲更多的是一種感覺,這種感覺別人無法體會,完善到自己的極限后,海闊天空的感覺,一份領悟,這種領悟將會使人受益終生,這也就是編程之美。

謹以此文表達對孫老師,還有兩位助教給予的幫助的感謝。

總結

以上是生活随笔為你收集整理的最大字段和 冲出暴力枚举的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。