通常DP
都是有模板的,先初始化,而後找到不一樣狀態下數值的關係,使得某個狀態可用另外一個狀態由一個固定的方式轉移而來,列出狀態轉移方程,這就是DP;html
例題
P1216 [USACO1.5]數字三角形 Number Trianglesc++
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
1 f[i][j]+=max(f[i-1][j-1],f[i-1][j]);
P1044 棧git
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
1 f[i]+=f[j]*f[i-j-1];
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
1 f[i]=min(f[i-1]+t[i],min(f[i-2]+t[i-1],f[i-3]+t[i-2]));
能夠看出,裸的DP是幾乎沒有難度的,固然某些題除外如P1004,P1280等題,值得思考。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揹包基本是套這個模板,但也不缺少頗有思考性的,如P2370,P2979,P1156,P4544(這個要單調隊列優化,但純揹包有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;//由於單調遞增,因此頭最小 } }
若是僅僅只是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是一種很優美暴力的作法呢
例題
樹形DP(我最不擅長)
蒟蒻真的不會啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!
其餘算法套DP(二分什麼的)
DAY3 T4 luogu P1772 最短路+DP(真的少見)
還有一些提升組的就不列了(如斜率優化,數位,插頭DP)