【五一qbxt】day3 動態規劃

動態規劃

引例:ios

斐波那契數列:
邊界條件:f0=0;c++

                  f1=1;算法

可以直接被求出值的狀態數組

不須要計算其餘斐波那契數列的值直接能夠獲得結果;數據結構

轉移方程:fn=fn-1+fn-2如何用已有狀態求出未知狀態學習

前幾項:0,1,1,2,3,5,8,13……大數據

狀態:f1,f2,f3……fn;(要求的未知的量)優化

DAG<=>無後效性??(暫時不用管什麼東西)ui

通項公式:spa

實現DP

法1:記憶化搜索(通常來講用不上qwq)

會多開一個記錄是否算過的數組,故空間會比下面兩種大一點

法2:順着推:用本身去推別人

法3:倒着推:用別人更新本身

#include<iostream>
#include<cstdio>
using namspace std; 

int n,f[233];
/*斐波那契數列:倒着推
int main(){
    cin>>n;
    f[0]=0;
    f[1]=1;
    for(int a=2;a<n;a++)
    {
        f[a]=f[a-1]+f[a-2];
    }
    cout<<f[n]<<endl;
}*/
/*斐波那契數列:順着推 
int main(){
    cin>>n;
    f[0]=0;
    f[1]=1;
    for(int a=0;a<n;a++){
        f[a+1]+=f[a];
        f[a+2]+=f[a];
    }
    cout<<f[n]<<endl;
} */
/*搜索  O(f(n)) 與斐波那契數列第n項大小成正比 約爲1.6^n 
int dfs(int n){
    if(n==0) return 0;
    if(n==1) return 1;
    return dfs(n-1)+dfs(n-2);
}

int main(){
    cin>>n;
    cout<<dfs(n)<<endl;
    
    return 0
} */ 
/*記憶化搜索  O(n) 
在計算過程當中,保證f0~fn每一個數只被算一次

bool suan_le_mei[n];
 
int dfs(int n){
    if(n==0) return 0;
    if(n==1) return 1;
    if(suan_le_mei[n]) return f[n];//判斷是否算過 
    
    suan_le_mei[n]=true;
    f[n]=dfs(n-1)+dfs(n-2);
    
    return f[n];
}

int main(){
    cin>>n;
    cout<<dfs(n)<<endl;
    
    return 0
}*/ 
 

常見DP種類:

數位DP-50%

樹形DP-50%

狀壓DP-50%

區間DP-50%

(有套路^)

其餘DP-80%

(無規律^)

考不到的DP插頭DP,博弈論DP,呸機率比較小

常見優化:

單調性優化

矩陣乘法優化

其餘優化

放棄學習優化

看的一懵一懵的

數位DP

什麼叫作數位DP?

按照數字的位數劃分轉移階段

轉移方式:枚舉下一位數字填什麼

限制條件:數位的上下界要求

讀入兩個正整數l,r,問從l~r有多少個數?

顯然ans=r-l+1;

可是,咱們的鐘神拒絕日常,他要不同凡響,他要用數位DP作qwq

第一步:轉化:[0,r]數的個數-[0,l-1]的數的個數=>轉化成解決[0,x]有多少數=>有多少個v使得0<=v<=x;

x=>xn xn-1 xn-2……x0

v=>vn vn-1vn-2……v0(至多n位)給每位填上0~9的數,看有多少種方案知足v<=x

從最高位=>最低位

舉個栗子:填vn-3時,前面的都填好了

那麼v的前面的三位必須保證小於等於x的前三位,那麼爲了使v<=x,則須要:

分兩種狀況:1.x的前三位大於v的前三位=>vn-3能夠填任何數

                      2.x的前三位等於v的前三位=>0~xn-3

  

f[i][j(0/1)] 表明這種狀況的方案數

i:已經填好了第i位,j=>0/1

j=0=>xnxn-1……xi>vnvn-1……vi

j=1=>xnxn-1……xi=vnvn-1……vi

轉移:枚舉第i-1位填什麼

填第i位,從i+1位轉移過來.

邊界:第n+1位(相等且沒有數)f[n+1][1]=1;(都填0)

#include<iostream>

using namespace std;

int l,r,z[233];
int f[2333][2]; 

int solve(int x){
    int n=0;
    while(x){//求出x的每一位(最後有n位)(從0下標開始) 
        z[n]=x%10;
        x/=10;
        n++;
    }
    n--;
    memset(f,0,sizeof(f));//須要作兩次DP,故要把數組清空 
    
    f[n+1][1]=1;//邊界條件:第n+1位顯然都填0,因此相等的方案數爲1
               //不相等的方案數爲0 
    
    for(int a=n;a>=0;a--)//枚舉要填的第a位
    {
        for(int b=0;b<=1;b++)//考慮相等仍是小於分兩種討論qwq
        if(b==0){//x>v 
            for(int c=0;c<=9;c++)
              f[a][0]+=f[a+1][b]; 
        } 
        else {
            for(int c=0;c<=z[a];c++){
                if(c==z[a])f[a][1]+=f[a+1][b]//c==z[a],表明填上這個數後v=x 
                else f[a][0]+=f[a+1][b];//c!=z[a],表明填上這個數後v<x 
            }
        }
     } 
    return f[0][0]+f[0][1]; 
}

int main(){
    
    cin>>l>>r; 
    
    cout<<solve(r)-solve(l-1)<<endl;
    
    return 0;
} 

problem2:

求[l,r]中的數的數位之和

#include<iostream>
#include<cstring>

using namespace std;

int l,r,z[233];
int f[2333][2]; 
int g[2333][2];//ij表達與f相同 

int solve(int x){
    int n=0;
    while(x){//求出x的每一位(最後有n位)(從0下標開始) 
        z[n]=x%10;
        x/=10;
        n++;
    }
    n--;
    memset(f,0,sizeof(f));//須要作兩次DP,故要把數組清空 
    memset(g,0,sizeof(g));
    
    f[n+1][1]=1;//邊界條件:第n+1位顯然都填0,因此相等的方案數爲1,不相等的方案數爲0
    g[n+1][1]=0; 
    
    for(int a=n;a>=0;a--)//枚舉要填的第a位
    {
        for(int b=0;b<=1;b++)//考慮相等仍是小於分兩種討論qwq
        if(b==0){//x>v 
            for(int c=0;c<=9;c++){
                f[a][0]+=f[a+1][b]; 
                g[a][0]+=g[a+1][b]+f[a+1][b]*c;//先加上前a+1位的和,而後對於第a位,能夠填0~9任一,那麼每填一個數c,每一種方案的和都+c,故要用方案數*c 
            }
        } 
        else {
            for(int c=0;c<=z[a];c++){
                if(c==z[a]){
                f[a][1]+=f[a+1][b];
                g[a][1]+=g[a+1][b]+f[a+1][b]*c; 
                }//c==z[a],表明填上這個數後v=x 
                else {
                f[a][0]+=f[a+1][b];//c!=z[a],表明填上這個數後v<x 
                g[a][0]+=g[a+1][b]+f[a+1][b]*c; 
            }
            }
        }
     } 
    //return f[0][0]+f[0][1]; 
    return g[0][0]+g[0][1]; 
}

int main(){
    
    cin>>l>>r; 
    
    cout<<solve(r)-solve(l-1)<<endl;
    
    return 0;
} 

problem3:

求在[l,r]中的知足相鄰兩個數字之差至少爲2的數有多少個

多一維條件,多一個狀態。

f[i][j][k]已經填好了第i位;j:0/1表示</=;k:第i位填了k

保證第i位和第i+1位的數字大小差至少2;

windy數qwq

 

樹形DP:

什麼是樹形DP:

按照樹從根往下或者葉子網上劃分階段

轉移方式:集合葉子或者父親的信息

限制條件:不詳

給你一個n個點的數,問有多少個點???閒圈啊qwq

 

f[i]以i爲根的子樹中共有多少個點

邊界條件:葉節點的f爲1;f[葉子]=1;

f[p]=f[p1]+f[p2]+1;

 

轉移方程 f[i]=f[son1]+f[son2]+……+f[sonn]+1

eg:

給我一個n個點的數,求這棵樹的直徑是多少?

直徑:在一棵樹上找到兩個點,使得這兩點間的距離最遠。

樹路徑長度,從根結點到某結點的邊數

考慮一棵子樹內的直徑qwq

路徑長這樣:

 

能夠把直徑當作從某一點向下走了兩條路而後合起來

找兩種向下走最長的兩條路徑

即從一點向下走最長能夠走多少和次長能夠走多少。

狀態定義:f[i][0]i向下最長那條路長度是多少

          f[i][1]i鄉下的次長的那條路的長度

 

轉移方程:(n個兒子)

f[p][0]=max(f[son1][0],f[son2][0]……,f[sonn][0])+1;

假設最大的是son2,那麼顯然咱們不能再走son2了

f[p][1]=max(f[son1][0],f[son3][0],……f[sonn][0])+1;

ans=max(f[i][0]+f[i][1]);(枚舉全部的i找最大的一個)

區間DP:

知足:

合併相鄰的

把全部的合併成一個東西

的爲區間DP;

eg:合併石子

n堆石頭,a1,a2……an,能夠合併相鄰的兩堆石子,合併的代價:合併的這兩堆的石頭數之和。(合併n-1次qwq)如今把這n堆石頭合併爲1堆,使代價最小。

狀態:f[l][r]表明把第l堆石子到第r堆石子合併爲一堆石子的最小代價

要求的即爲f[1][n]

邊界條件:l=r時答案爲0;即f[i][i]=0;

轉移方程:f[l][r]=min(f[l][p]+f[p+1][r]+sum[l][r])

合併過程並無改變石頭的順序,最後必定爲把兩堆合併爲一堆,那麼必定能夠找到一條分界線p,左邊爲al~ap右邊爲ap+1,~ar

左邊合併的最小代價即爲f[l][p],右側爲f[p+1][r];

枚舉一個點p把左右分別合併成一堆,而後再合併成一堆

複雜度:O(n3)(n的極限約爲100~200左右)

 

石子合併:洛谷p1880

處理第1堆和第n堆相鄰的狀況

很天然的想法:

 

最後的答案可能不是f[1][n]

an和a1相鄰,a1,a2不相鄰的狀況:f[2][n+1]

再加一個a2:

繼續向後面加:(在原來序列基礎上補上一個相同的序列)

取min

原理:

對於一個環形排序的n堆物品,要把他們合併爲一堆,須要合併n-1次,那麼有一條邊始終沒有用到,咱們能夠看是斷開的,也就是說,咱們能夠不看它,把長度延長爲原來的兩倍即爲枚舉要斷開的邊是哪一條


下午

狀態壓縮DP

什麼叫狀壓DP:

按照選取集合的狀態劃分轉移階段

轉移方式:枚舉下一個要選取的物品

限制條件:不詳

 

N個點:(x1,y1)(x2,y2)……(xN,yN

從一號點出發,保證每一個點都至少走一次,使得走過的路徑的長度之和最短

TSP問題(旅行商問題)很是經典的NP-hard問題(想解決掉這種問題,至少爲2n,n在指數上)qwq

一個點有沒有必要走兩次???沒有=>最優狀況下每一個點只需去一次

應該怎麼走???直接拉一條線段過去<=>兩點間線段最短

已走:1 2 5

未走:3 4 6

如今的位置在5號點,那麼接下來的狀態:

 

如何把集合表示到代碼裏面去:(如何用一個數表明一個集合)構造一個n位的二進制數,

<=>{1,4,5}

轉成10進制=>25

每一個元素只可能 在/不在 集合中兩種狀況,第i位在集合中爲1,不在集合中爲0

設計這樣一個DP方法:

f[s][i]

s:n位的二進制數,已經走到過的點

 

:{1,2,4,6}說明已經走過1,2,4,6點,對應二進制=>101011

i:如今停留在i點

初始化:f[1][1]=0;

轉移:

從i點到j點,j屬於1~n;

而且要判斷j∉s

f[s∪{j}][j]<=f[s][i]+dis[i][j];

p1171

#include<cstdio>
#include<iostream>

using namespace std;

const int manx=20;

int n;
double f[1<<maxn][maxn];//f[2的n次方][n]; 

double dis(int i,int j){
    return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
} 

int main(){
    
    cin>>n;
    for(int a=0;a<n;a++)//二進制最低位是第0位 
    cin>>x[a]>>y[a];
    for(int a=0;a<(1<<n);a++)
      for(int b=0;b<n;b++)
        f[a][b]=1e+20;
    f[1][0]=0;//初始化 
    //轉移:較小的s=>較大的s,從小到大枚舉s
    
    for(int s=1;s<(1<<n);s++)
      for(int i=0;i<n;i++)
         if(((s>>i)&1)==1) 
           for(int j=0;j<n;j++)
             if(((s>>j)&1)==0) 
               f[s|(1<<j)][j]=min(f[s|(1<<j)][j],f[s][i]+dfs(i,j));
    double ans=le+20;
    for(int a=1;a<n;a++)
    ans=min(ans,f[(1<<n)-1][a]);
    
    cout<<ans<<endl; 
}

狀壓DP:

時間複雜度:O(n2*2n) 空間複雜度:O(2n*n)能接受的數據範圍:n<=20(大約是,有時候能夠達到22)

通常機子1s能夠跑107~109看機子不一樣吧qwq

 一些數據範圍推複雜度和算法:

n≤12=>暴搜

n≤20(22)=>狀壓

n≤32  =>放棄

n≤50  =>放棄

n≤100 =>n3

n≤1000 =>n2

n≤105=>數據結構,線段樹

n≤106=>線性的

n>106=>考慮O(1)的算法

在這普通的一天,我穿着普通的鞋,很普通地來到這普通的酒店,打開普通的電腦,找點普通的感受,學一點我最愛的普通DP

普通DP

題目多一個條件=>多一個維度

first:

 邊界條件:

第一行和第一列所有爲1:f[i][1]=f[1][j]=1;

轉移:f[i][j]=f[i-1][j]+f[i][j-1];

上次zt鍾神也講的一個東西:

n行m列:從左上走到右下的方案數:Cn+m-2n-1

數字三角形:

洛谷p1216

每次能夠向下或右下走。

這條路徑的權值:這條路上全部數的和

求最大值

狀態f[i][j]表示在走到(i,j)這個點最大的權值是多少;

邊界條件:f[1][1]=a[1][1];

轉移方程式:f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];

數字三角形改:

從a[1][1]向下或向右下走,使得路徑權值mod m後的值最大

n,m<=100;

感性理解:

爲何不給大一點???

1.給大了作不了    m可能也是一個維度

2.出題人懶得給大數據(wz啊)

邊界:f[1][1][a[1][1]]=true;

狀態:f[i][j][k]走到i行j列%m==k是可能or不可能

轉移:f[i][j][k]=f[i-1][j-1][k-a[i][j]]orf[i-1][j][k-a[i][j]]

注意k-a[i][j]可能會爲負,故要取模

ans最後一行中可能出現的k中最大的

codevs 上的一道題

下一個問題:

最長上升子序列

不細講了你們都作過qwq:

給定一個序列a1,a2,a3……an

找出一個序列b1,b2……bk使得:ab1<ab2<……abk

f[i]表示找一個以i號點結尾的最長上升子序列長度

轉移:找一個j使得1<=j<i,aj<ai枚舉f[i]=max f[j]+1;

複雜度O(n2

增強一下:數據範圍n<=105

線段樹qwq沒學好啊

假設v=max(a1,a2,a3……,an)

建一棵大小爲n的線段樹(按值來作左右劃分)

線段樹:區間詢問最大值&單點修改???

有點特殊的DP:揹包問題

揹包九講

最基本揹包問題:有n個物品,第i個物品價值爲w[i],體積爲v[i],如今有一個揹包大小爲m,在不超過揹包容積狀況下,價值最大。

01揹包

狀態定義:f[i][j]把前面i個物品是否放進揹包已經考慮完了,揹包中已經使用了j的體積的最大價值。

轉移:若是不放進揹包=>f[i][j]=>f[i+1][j] 0轉移

      若是放進揹包=>f[i][j] +w[i+1]=>f[i+1][j+v[i+1]] 1轉移

f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);

無窮揹包(徹底揹包)

有n個物品,第i個物品價值爲w[i],體積爲v[i],每種物品都有無數個,問同上;

未優化:

轉移方程:f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i])O(nm)

存在轉移鏈:

f[i][j]<=f[i][j-v[i]]<=f[i][j-2v[i]];

有限揹包:

作法:枚舉每一個物品要用多少個(同無窮揹包未優化的操做)

eg:求在[l,r]中知足各位數字之積爲k的數有多少個

l,r<=1018

再加一維:f[i][j][k]填到i位,j=0  => 「<」,j=1  => 「=」,乘積爲k;

k:max:918會炸,由於0~9中質因數只有2,3,5,7,故k=2a*3b*5c*7d

拆維度qwq拆的我一臉懵逼:

有的時候維度多並不表明會炸掉,有時反而或許能夠下降複雜度qwq。

 很神奇,你竟然看到了最後,tql

那麼接下來就是鍾神語錄了(算是看到最後的小彩蛋吧qwq)

"雖然我不知道你說的是什麼,但確定是錯的"

「這個題我是從vijos看到的,不知道這個oj還活着沒有」

「當你發現你這樣作作不出來時,加一個維度,還作不出來,再加一個維度QWQ」

相關文章
相關標籤/搜索