1D1D動態規划優化
1D/1D 動態規划優化初步
所謂1D/1D 動態規划,指的是狀態數為O(n),每一個狀態決策量為O(n)的動態規划方程。
直接求解的時間復雜度為O(n2),但是,絕大多數這樣的方程通過合理的組織與優化都是可
以優化到O(nlogn)乃至O(n)的時間復雜度的。這里就想講一講我對一些比較初步的經典的
優化方法的認識。
本文中不想進行過多的證明與推導,主要想說明經典模型的建立、轉化與求解方法。
由於本人認識與水平相當有限,如果出現什么錯誤與疏漏,還請大牛多多指正。另外,也
希望大牛們更多地向我們介紹一下有關動態規划優化的更深入的東西。
本文中使用兩種方式表示一個函數:f(x)與f[x],用方括號表示的函數值可以在規划之前
全部算出(常量),而用圓括號表示的函數值必須在規划過程中計算得到(變量)。無論是什
么函數值一經確定,在以后的計算中就不會更改。
我們總是沿着“f(x)的最優決策是什么”這個思路進行思考,下面我們換一個角度,
思考對於一個已經計算出來的狀態f(j),“f(j)能夠更新的狀態有哪些”。這樣,每一步過程中
某些狀態的決策可能不是最優的,但是當算法結束的時候所有狀態對應的決策一定是最優
的。
一開始,只有f(1)的函數值被計算出來,於是所有狀態的當前最優決策都是1。
111111111111111111111111111111111111111111111111111111111111111
現在,顯然f(2)的值已經確定了:它的最有決策只能是1。我們用決策2 來更新這個決策
表。由於決策單調性,我們知道新的決策表只能有這樣的形式:
111111111111111111111111111111222222222222222222222222222222
這意味着我們可以使用二分法來查找“轉折點”,因為如果在一個點x 上,如果決策2 更
好,則所有比x 大的狀態都是決策2 更好;如果x 上決策1 更好,則所有比x 小的狀態都是
決策1 更好。
現在決策1 和決策2 都已經更新完畢,則f(3)業已確定,現在用決策3 來更新所有狀態。
根據決策單調性,現在的決策表只能有以下2 種類型:
11111111111111111111111111111111122222222222222222233333333333
1111111111111111111111111333333333333333333333333333333333333
而這樣的決策表示絕對不會出現的:
111111111111333333333333333333322222222222222222222222222222,不可能。
那么,我們的更新算法就是:
1、 考察決策2 的區間[b,e]的b 點上是否決策3 更優,如果是,則全部拋棄決策2,將此
區間划歸決策3;如果否,則在決策2 的區間[b,e]中二分查找轉折點。
2、 如果第1 問的回答是“是”,則用同樣的方法考察決策1。
推演到這一步,相信決策單調性的實現算法已經明了了:使用一個棧來維護數據,占中的
每一個元素保存一個決策的起始位置與終了位置,顯然這些位置相互連接且依次遞增。當插
入一個新的決策時,從后到前掃描棧,對於每一個老決策來說,做這樣兩件事:
1、 如果在老決策的起點處還是新決策更好,則退棧,全額拋棄老決策,將其區間合並至
新決策中,繼續掃描下一個決策。
2、 如果在老決策的起點處是老決策好,則轉折點必然在這個老決策的區間中;二分查找
之,然后新決策進棧,結束。
由於一個決策出棧之后再也不會進入,所以均攤時間為O(1),但是由於二分查找的存在,
所以整個算法的時間復雜度為O(nlogn)。
noi2009a 詩人小G對應的1D1D動態規划優化分析
1 //不要去洛谷上面提交,洛谷的這題數據有問題,去codeVs 2 #include<cstdio> 3 #include<cstring> 4 #define ll long double 5 //node[i]代表i,里面的l代表決策的作用起點,r代表決策的作用終點,p是決策的值 6 struct node{int l,r,p;}q[100100]; 7 #define MAX 1000000000000000000LL 8 #define N 100100 9 ll sum[N],f[N]; 10 int n,l,p,T; 11 char ch[35]; 12 //求|y|^p的函數 13 ll pow(ll y){ 14 if(y<0)y=-y; 15 ll ans=1; 16 for (int i=1;i<=p;i++) ans*=y; 17 return ans; 18 } 19 //計算不協調度 20 ll calc(int x,int y){ 21 return f[x]+pow(sum[y]-sum[x]+(y-x-1)-l); 22 } 23 //二分查找找決策轉折點 24 //node表示老決策點,x表示新決策點的值 25 //這個函數就是在老的決策點的范圍中尋找新的決策點的值 26 int find(node t,int x){ 27 int l=t.l,r=t.r; 28 while(l<=r){//當頭小於等於尾,也就是還有數可以查找的時候 29 //mid=(頭+尾)/2 30 int mid=(l+r)>>1; 31 //小於的情況,也就是新的決策點更有,我們就要一直往前繼續擴展新的決策點的起點, 32 //直到老決策點比較好的時候 33 if (calc(x,mid)<=calc(t.p,mid)) r=mid-1; 34 //大於的情況,也就老決策點比較好的時候,我們往后搜索新決策點的作用域的起點 35 else l=mid+1; 36 } 37 //返回頭 38 return l; 39 } 40 41 void dp(){ 42 //用數組模擬棧,頭置為1,尾置為0 43 int head=1,tail=0; 44 //將第一個決策入棧,決策1的起點為0,終點為n,決策的初始值置為0,因為初始決策就是0 45 q[++tail]=(node){0,n,0}; 46 //求取f[i] 47 for (int i=1;i<=n;i++){ 48 //如果棧頭結點上的決策作用終點小於i,說明這個決策已經無法對i進行作用,所以我們要換新的決策 49 //head<=tail棧的頭小於等於尾,說明棧里面還有新的決策,那就換上新的決策 50 //因為head++,所以此時頭節點是指向新的決策 51 if(q[head].r<i&&head<=tail) head++; 52 //用新的可以作用i位置的決策來計算f[i]的值,這樣計算出來的f[i]的值就是最優值 53 f[i]=calc(q[head].p,i); 54 //calc(i,n)表示通過值為i的決策去計算f[n]的值 55 //calc(q[tail].p,n)表示通過舊決策q[tail].p去計算f[n]的值 56 //calc(i,n)<=calc(q[tail].p,n)表示值為i的決策優於老決策,我們才進行下一步 57 //不然,如果老決策更好,我們根部用不着換決策 58 59 //每一個被計算出來的f[i],都會使i成為新的決策,因為我有了f[i]的值, 60 //所以我可以用f[i]的值幫助計算別的f[k](k>i),所以f[i]就是新的決策 61 62 /* 63 比如說我們的樣例1中,當i==3時,我們通過 q[head].p=2這個決策把f[3]算出來了 64 比如說這里的n是4,如果calc(3,4)<=calc(2,4),那說明值為3的這個新決策更好, 65 我們就要在決策2的作用區間里面找出決策3的作用區間 66 */ 67 68 69 //如果新決策更好,找出新決策的作用范圍 70 //確定新決策點的終點 71 if (calc(i,n)<=calc(q[tail].p,n)){ 72 //如果棧的頭小於等於尾,head<=tail,說明棧里有元素 73 //calc(q[tail].p,q[tail].l)表示用舊決策點決策值來決策舊決策點的起點 74 //calc(i,q[tail].l)用新決策點來決策舊決策點的起點 75 //如果后者小於前者,說明新決更好,那就要在更廣闊的范圍里面去尋找新決策點的決策范圍 76 //換句話說,就是當新決策點的作用范圍覆蓋了老決策點,我們要到比舊決策點更老點決策點里面尋找新決策點的作用方位 77 while(head<=tail&&calc(q[tail].p,q[tail].l)>=calc(i,q[tail].l)) tail--; 78 //head>tail說明棧已經為空了,就是說現在新決策點最好,所以把新決策點入棧 79 if(head>tail)q[++tail]=(node){i,n,i}; 80 else{ 81 //否則,我們就要去尋找新決策點的作用范圍 82 //x返回新決策點范圍的起點 83 int x=find(q[tail],i); 84 //舊決策點的尾部置為x-1 85 q[tail].r=x-1; 86 //將新決策點入棧 87 q[++tail]=(node){x,n,i}; 88 } 89 } 90 } 91 } 92 93 int main(){ 94 scanf("%d",&T); 95 while(T--){ 96 scanf("%d%d%d",&n,&l,&p); 97 for (int i=1;i<=n;i++) scanf("%s",ch),sum[i]=sum[i-1]+strlen(ch); 98 dp(); 99 if(f[n]>MAX) 100 puts("Too hard to arrange"); 101 else 102 printf("%lld\n",(long long)f[n]); 103 puts("--------------------"); 104 } 105 return 0; 106 }