DP主要的核心就是对于每道题专属的状态转移方程
动态规划过程是:
每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划(DP)。
基本思想与策略
基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。
动态规划的算法设计
1:找出最优解的性质,并描述其结构特征
2:递归定义最优值
3:以自底向上的方式计算最优值
4:根据计算最优值时得到的信息构造出最优解
能采用动态规划求解的问题的一般要具有3个性质
(1) 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
(2) 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
(3) 有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)动态规划将原来具有指数级时间复杂度的搜索算法改进成了具有多项式时间复杂度的算法。其中的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。
使用动态规划求解问题,最重要的就是确定动态规划三要素
(1)问题的阶段
(2)每个阶段的状态
(3)从前一个阶段转化到后一个阶段之间的递推关系。
动态规划的具体步骤:
(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
(2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
(3)确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
(4)寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
”简单“DP
1.股票买卖(P2569 [SCOI2010]股票交易):OpenJudge - 8464:股票买卖
(1)如果没有买卖两次的限制:
#include<bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin >> T;
while(T--){
int N; cin >> N;
int a[N+1];
for(int i=1;i<=N;i++)
cin >> a[i];
int dp[N+1][2];
dp[1][0]=0;
dp[1][1]=-a[1];
for(int i=2;i<=N;i++){
dp[i][0]=max(dp[i-1][1]+a[i],dp[i-1][0]);
dp[i][1]=max(dp[i-1][0]-a[i],dp[i-1][1]);
}
int max1=max(dp[N][1]+a[N],dp[N][0]);
cout << max1;
if(T) cout << endl;
}
return 0;
}
//dp[n][1],dp[n][0]表示到第n天手中还有,手中没有股票的利润
//dp[n][1]=max((dp[n-1][0]+(n-1)买入),dp[n-1][1])
//dp[n][0]=max((dp[n-1][1]+(n-1)卖出),dp[n-1][0])
//
//
(2):(12条消息) 股票买卖问题_u011402017的博客-CSDN博客_股票买卖的问题
这个写的很好
有两次的限制:
#include<bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin >> T;
while(T--){
int N; cin >> N;
int a[N+1];
for(int i=1;i<=N;i++)
cin >> a[i];
int dp[N+1][2][2];
dp[1][1][0]=dp[1][0][0]=0;
dp[1][0][1]=dp[1][1][1]=-a[1];
for(int i=2;i<=N;i++){
for(int k=0;k<=1;k++){
dp[i][k][0]=max(dp[i-1][k][0],dp[i-1][k][1]+a[i]);
if(k==1) dp[i][k][1]=max(dp[i-1][k][1],-a[i]);
else dp[i][k][1]=max(dp[i-1][k][1],dp[i-1][k+1][0]-a[i]);
}
// dp[i][1][0]=max(dp[i-1][1][0],dp[i-1][1][1]+a[i]);
// dp[i][0][0]=max(dp[i-1][0][0],dp[i-1][0][1]+a[i]);
// dp[i][1][1]=max(dp[i-1][1][1],-a[i]);
// dp[i][0][1]=max(dp[i-1][0][1],dp[i][1][0]-a[i]);
}
cout << dp[N][0][0];
if(T) cout << endl;
}
return 0;
}
//dp[n][1],dp[n][0]表示到第n天手中还有,手中没有股票的利润
//dp[n][1]=max((dp[n-1][0]+(n-1)买入),dp[n-1][1])
//dp[n][0]=max((dp[n-1][1]+(n-1)卖出),dp[n-1][0])
//
//
2.区间DP:(能量项链):(但是有WA。。)
#include<bits/stdc++.h>
using namespace std;
int dp[300][300];
int main(void){
int N;
cin >> N;
int a[2*N];
for(int i=0;i<N;i++){
cin >> a[i];
a[N+i]=a[i];
}
int max1=0;
for(int j=1;j<N;j++)
for(int i=0;i<N;i++)
for(int k=0;k<j;k++){
dp[i][i+j]=max(dp[i][i+j],dp[i][i+k]+dp[i+k+1][i+j]+a[i]*a[i+k+1]*a[i+j+1]);
max1=max(max1,dp[i][i+j]);
}
cout << max1;
return 0;
}
//dp[0][n-1]=dp[0][i]+dp[i+1][n-1]+max(a[i]*a[0]*a[0],a[i]*a[i]*a[0])
//dp[i][j]=dp[i][k]+dp[k+1][j]+max(a[k]*a[i]*a[i],a[k]*a[k]*a[i])
//dp[i%N][(i+j)%N]=max(dp[i%N][(i+j)%N],dp[i%N][(i+k-1)%N]+dp[(i+k)%N][(i+j)%N]+a[i%N]*a[(i+k)%N]*a[(i+j+1)%N]);
//
//
这样就ok:
#include<bits/stdc++.h>
using namespace std;
int dp[202][202];
int main(void){
int N;
cin >> N;
int a[2*N];
for(int i=0;i<N;i++){
cin >> a[i];
a[N+i]=a[i];
}
int max1=0;
for(int i=1;i<2*N-1;i++)
for(int j=i-1;i-j<N && j>=0;j--){
for(int k=j;k<i;k++)
dp[j][i]=max(dp[j][i],dp[j][k]+dp[k+1][i]+a[j]*a[k+1]*a[i+1]);
max1=max(max1,dp[j][i]);
}
cout << max1;
return 0;
}
3.树形DP(没有上司的舞会)
树形DPblog: 树形$dp$学习笔记 - _Lancy - 博客园 (cnblogs.com)
完全自己写的:
#include<bits/stdc++.h>
using namespace std;
struct Tree{
int val,fa,wei,bok;
Tree(int val=0,int fa=0,int wei=0,int bok=0):val(val),fa(fa),wei(wei),bok(bok){}
};
queue<int> q;
int main(void){
int N;
cin >> N;
Tree a[N+1];
a[0].fa=-1;
int book[N+1]={0}; //标记0为叶节点(待用) 1不为叶节点(待待用) -1为使用过
for(int i=1;i<=N;i++){
cin >> a[i].val;
}
for(int i=1;i<=N-1;i++){
int temp1,temp2;
cin >> temp1 >> temp2;
a[temp1].fa=temp2;
book[temp2]=1;
}
for(int i=1;i<=N;i++){
if(book[i]==0)
q.push(i);
a[a[i].fa].bok=1;
}
int max1=0;
while(!q.empty()){
int i=q.front();
if(book[i]==-1){
q.pop();
continue;
}
if(a[i].bok==1){
a[a[i].fa].bok=(a[i].val>a[i].wei)?1:0;
a[i].val=max(a[i].val,a[i].wei);
}
else{
if(a[i].val>=0)
a[i].val+=a[i].wei;
else
a[i].val=a[i].wei;
a[a[i].fa].bok=1;
}
// if(max1<a[i].val){
// cout << endl;
// cout << "max1:" <<max1 << " i:" << i << " a[i].val:" << a[i].val << endl;
// }
max1=max(max1,a[i].val);
if(a[i].fa!=-1){
q.push(a[i].fa);
a[a[i].fa].wei+=a[i].val;
book[a[i].fa]=0;
}
book[i]=-1;
q.pop();
}
cout << max1;
return 0;
}
别人:
f[x][0]表示以x为根的子树,且x不参加舞会的最大快乐值
f[x][1]表示以x为根的子树,且x参加了舞会的最大快乐值
则f[x][0]=sigma{max(f[y][0],f[y][1])} (y是x的儿子)
f[x][1]=sigma{f[y][0]}+h[x] (y是x的儿子)
vector<int> son[max]表示一个节点的儿子
(树的直径):某一结点的最长分成两部分:它向下与它到根节点加上根节点的最大长度
这个写的真的很好:(14条消息) 求树的直径算法_weixin_30895603的博客-CSDN博客
还有这个:(14条消息) 树的直径_Reaearcher-Du-CSDN博客_树的直径
自己:
#include<bits/stdc++.h>
using namespace std;
const int INF=10000000;
vector<int> G[100000];
vector<int> E[100000];
int dis[100000];
bool vis[100000];
void Init(void);
void dfs(int n);
int maxval,maxi;
int main(void){
int N;
cin >> N;
Init();
for(int i=1;i<=N-1;i++){
int t1,t2,t3;
cin >> t1 >> t2 >> t3;
G[t1].push_back(t2);
E[t1].push_back(t3);
G[t2].push_back(t1);
E[t2].push_back(t3);
}
memset(dis,INF,sizeof(dis));
dis[1]=0;
dfs(1);
int start=maxi;
Init();
memset(dis,INF,sizeof(dis));
dis[start]=0;
maxval=0;
dfs(start);
cout << maxval << endl;
return 0;
}
void Init(void){
memset(vis,0,sizeof(vis));
}
void dfs(int n){
vis[n]=1;
int size=G[n].size();
for(int i=0;i<size;i++){
int v=G[n][i];
if(vis[v]==0){
dis[v]=dis[n]+E[n][i];
if(maxval<dis[v])
maxi=v;
maxval=max(maxval,dis[v]);
dfs(v);
}
}
}
(毛毛虫)
4.背包问题:
(14条消息) 01背包问题相关优化大全(二维到一维)_曼切斯特的流氓的博客-CSDN博客
完全背包:反向改正向:(14条消息) 完全背包问题(详细解答)_曼切斯特的流氓的博客-CSDN博客_完全背包问题
分组背包:物品个数有限(二进制优化)
(14条消息) 背包九讲——全篇详细理解与代码实现_良月澪二的博客-CSDN博客_背包九讲
若要求恰好装满(非装满全赋值为正无穷)
6.状压DP:二进制表状态
(不能。。。的玉米田)
(互不侵犯):DP[i][j][num]
(炮兵阵地)
(动物园)
tip:异或操作
7.习题:
Misunderstood … Missing:具有后效性则从后开始DP
计算直线的交点个数:n条设有m条平行线,dp[n]=dp[n-m]+n*m
dp[n][m]表示以n为边数m为点数是否成功
8.bitset
9.dp与网络流