算法之DP

通常DP

都是有模板的,先初始化,而後找到不一樣狀態下數值的關係,使得某個狀態可用另外一個狀態由一個固定的方式轉移而來,列出狀態轉移方程,這就是DP;html

例題

P1216 [USACO1.5]數字三角形 Number Trianglesc++

1 f[i][j]+=max(f[i-1][j-1],f[i-1][j]);
方程

P1044 棧git

1 f[i]+=f[j]*f[i-j-1];
方程

P2800 又上鎖妖塔算法

1 f[i]=min(f[i-1]+t[i],min(f[i-2]+t[i-1],f[i-3]+t[i-2]));
方程

P1057 傳球遊戲數組

能夠看出,裸的DP是幾乎沒有難度的,固然某些題除外如P1004P1280等題,值得思考。ide


 

揹包問題

揹包屬於基礎DP,但拓展性是最高的。優化

具體能夠看dd大牛的《揹包九講》url

如下先講01揹包spa

1 f[v]=max{f[v],f[v-c[i]]+w[i]};

上面的是01揹包的轉移方程,但v是從V...c[i]的。.net

爲何呢?這個方程表明第v個體積的物體的最大值=max(他本身自己的值,v-第i個物體的體積時的最大值+第i個物體的值)

例題

01揹包基本是套這個模板,但也不缺少頗有思考性的,如P2370P2979P1156P4544(這個要單調隊列優化,但純揹包有60-70分,題解在這裏);


 

線段樹單調隊列優化(都是用線段樹水過)

 首先,相信你們都知道什麼是移動窗口了吧(簡單得一匹好吧,線段樹修改查找,傻子都會),創建一個deque(頭尾均可進出,但常數能夠把你卡到80,連讀優都救不了我)

細節看註釋吧

#include<bits/stdc++.h>
using namespace std;
deque<int>q;
int n,m,x[2000005];
inline int read(){
	int ret=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
	while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
int main()
{
	cin>>n>>m;
	for (register int i=1;i<=n;i++) x[i]=read();
	cout<<0<<endl;//由於是前i個
	for (register int i=1;i<=n-1;i++)
	{
		while (!q.empty()&&x[i]<=x[q.back()]) q.pop_back();//維護單調遞增性,由於要求最小
		q.push_back(i);//放入
		while (q.back()-q.front()>=m) q.pop_front();//過時就彈出,deque存編號就很方便了	
		cout<<x[q.front()]<<endl;//由於單調遞增,因此頭最小
	}
}

P1725森♂之妖精

若是僅僅只是N^2DP相信你們均可以A出來,方程就是f[i]=max{f[i-j...i]}+a[i],但這明顯能夠優化好吧(把f數組插入線段樹裏(logn),在查詢(logn)),咱們只須要f[i-j...i]的最大值就能夠O(1)推了(是推),因此搞個單調隊列,像滑動窗口那樣作個單調遞減隊列,每次拿隊頭推便可

//這是我醜陋的優先隊列的代碼
#include<bits/stdc++.h>
using namespace std;
priority_queue<int>qx,qy;
const int MAXN=300005;
int ans[MAXN],N,L,R,f[MAXN],maxx;
inline int read()
{
    int ret=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
    while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
    return ret*f;
}
inline bool check(){
    if (!qy.empty()&&qx.top()==qy.top()) return 1;
    return 0;
}
int main(){
    N=read();L=read();R=read();
    for (int i=0;i<=N;i++) ans[i]=read();
    for(int i=1;i<=L-1;i++) qy.push(ans[i]);
    for(int i=L;i<=N;i++){
        qx.push(f[i-L]);
        if(i-R-1>=L) qy.push(f[i-R-1]);
        if (check()==1) qx.pop(),qy.pop();
        f[i]=qx.top()+ans[i];           
    }
     maxx=-99999999;
     for (int i=N-R+1;i<=N;i++) maxx=max(maxx,f[i]);
     cout<<maxx<<endl;
    return 0;
}
//這是luogu上我認爲(用deque)比較好看的單調隊列代碼
#include <bits/stdc++.h> 
using namespace std;
void read(int &x){
    int f=1,r=0;char ch;
    do ch=getchar();while(ch!='-'&&!isdigit(ch));
    if(ch=='-')f=-1,ch=getchar();
    do r=r*10+ch-48,ch=getchar();while(isdigit(ch));
    x=f*r;
}
int n,l,r;
struct info{
    int num,val;
    //分別記錄序號和數值,序號用來判斷是否超出範疇 
};
int f[200010];//f[i]表示到達點i時可得到的最大冰凍指數 
deque<info> q;//STL tql,單調隊列很是方便 
int a[200010];//a爲點i的冰凍指數(輸入數據)
int main() {
    cin>>n>>l>>r;
    for(int i=0;i<=n;i++)read(a[i]);
    for(int i=l;i<=n;i++){
        while(!q.empty()&&q.back().val<f[i-l])q.pop_back();//彈出較小值 
        q.push_back((info){i-l,f[i-l]});//放當前值 
        if(i-q.front().num>r-l)q.pop_front();//彈出超過範疇值 
        f[i]=q.front().val+a[i];//記錄最大值加上該點的冰凍指數放入f[i] 
    }
    cout<<*max_element(f+n-r+1,f+n+1)<<endl;
    //注意輸出的是這一段之間的最大值,由於這一段之間的每一位均可如下一步跳出n  
    return 0;
}

 最後,祭出NOIP2017 T4你覺得我會再寫嗎?其實我寫過了啊!

 例題

P2422(注意,並不是通常單調隊列啊,而且幾乎不能叫DP,但須要思考時間)


 

優先隊列優化(題少不怎麼打)

 不寫了,單調作的幾乎均可以用這個作

要看就看我停課集訓DAY1 T3


 

狀壓DP(就是變相暴力)

 說道狀壓DP,就不得不說著名的TSP旅行商問題了,這道題須要用到狀壓DP。

首先先上狀壓DP基操

這就是基操,狀壓DP通常都是把狀態用二進制數表示出來,通常都是取或不取之類的01狀態;

接下來說解旅行商問題。

首先肯定數組維數:2維,由於一維太難推了,且有後效性。二維表示什麼呢?分別表示當前狀態和最後走到何處。

而後就是枚舉狀態和循環了

第一層循環 i 枚舉每一個狀態

第二層循環 j 枚舉下一步到達的點

第三層枚舉從k點走到j點

而後if ((1<<(j-1)&i)==0)表示j點在此狀態還未走到,if ((1<<k-1)&i)表示k點在此狀態已走到。(廢話)

DP[j][i|(1<<j-1)]=min(DP[j][i|(1<<j-1)],DP[k][i]+LIS[k][j])就是對比走來和當前花費了(真的暴力-1s)

而後就是喜聞樂見的代碼放送了

#include<bits/stdc++.h>
using namespace std;
int DP[25][(1<<20)-1],N,MINN=2e9,LIS[25][25];
int main()
{
	cin>>N;
	int MAXN=(1<<N)-1;
	for (int i=1;i<=N;i++)
	 for (int j=1;j<=N;j++)
	  cin>>LIS[i][j];
	memset(DP,0x3f,sizeof(DP));
	DP[1][1]=0;//初始化 
	for (int i=0;i<=MAXN;i++)
	{
		for (int j=1;j<=N;j++)//到達j點 
		if ((1<<(j-1)&i)==0)//到達點未被走過 
		 for (int k=1;k<=N;k++)//從k過來 
		 { 
		  if ((1<<k-1)&i) DP[j][i|(1<<j-1)]=min(DP[j][i|(1<<j-1)],DP[k][i]+LIS[k][j]);
		 }		
	}
	for (int i=2;i<=N;i++) MINN=min(MINN,DP[i][(1<<N)-1]+LIS[i][1]);//要走回去啊,但不能不走(因此i從2開始) 
	cout<<MINN<<endl;
	return 0;
}

luogu的P3092是道很好的題目

但在此只放代碼,有註釋的

#include<bits/stdc++.h>
using namespace std;
int m,n,a[100010],sum[100010],c[20],b[20],f[1<<16],k,maxx=-1,sum1;
int main(){
    cin>>k>>n;
    for(int i=1;i<=k;i++) cin>>c[i],sum1+=c[i];
    for(int i=1;i<=n;i++) cin>>a[i],sum[i]=sum[i-1]+a[i];
    for(int i=0;i<=(1<<k)-1;i++)//硬幣狀態
        for(int j=1;j<=k;j++)
            if((i&1<<j-1)){//沒有被選
                int wz=f[i^1<<j-1];
                wz=upper_bound(sum+1,sum+n+1,sum[wz]+c[j])-sum;//買連續一段店鋪
                f[i]=max(f[i],wz-1);//更新狀態
            }
    for(int i=0;i<=(1<<k)-1;i++)
        if(f[i]==n){//個數知足
            int sum=0;
            for(int j=1;j<=k;j++) if(i&1<<j-1) sum+=c[j];//若是是1表明用了
            maxx=max(maxx,sum1-sum);//剩下最大價值
        }
    if(maxx<0) cout<<-1<<endl;else cout<<maxx<<endl;
    return 0;
}

果真,狀壓DP是一種很優美暴力的作法呢

例題

P1879P1896


 

樹形DP(我最不擅長)

 蒟蒻真的不會啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!


 

其餘算法套DP(二分什麼的)

NOIP2017 T4  二分加單調隊列+DP題解

 DAY3 T4 luogu P1772 最短路+DP(真的少見)

 


 

還有一些提升組的就不列了(如斜率優化,數位,插頭DP)

相關文章
相關標籤/搜索