动态规划dp
基础动态规划
题意:给出一个数字三角形(尖朝上),从顶端开始穿过三角形,向下移动到两个对角相邻的中的一个数字,直到到达底部,求沿途到访数字的总和
思路:从上到下对每个数字求最大值即可,最大值就是取上面两个值的最大值就行了,注意左右两边只能取一个值
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<iomanip>
using namespace std;
typedef long long ll;
const long long ll_inf=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
const double pi=acos(-1.0);
const int Mod=1e9+7;
const int Max=350;
//for(int i=0;i<n;i++)
//cout<<setw(8)<<fixed<<setprecision(3);
//ios::sync_with_stdio(false);
int main(){
int n;
cin>>n;
int a[Max][Max];
for(int i=0;i<n;i++){
for(int j=0;j<i+1;j++){
cin>>a[i][j];
}
}
for(int i=1;i<n;i++){
for(int j=0;j<i+1;j++){
if(j==0)
a[i][j]=a[i-1][j]+a[i][j];
else if(j==i)
a[i][j]=a[i-1][j-1]+a[i][j];
else
a[i][j]=max(a[i-1][j-1]+a[i][j],a[i-1][j]+a[i][j]);
}
}
int ans=0;
for(int i=0;i<n;i++){
ans=max(a[n-1][i],ans);
}
cout<<ans;
}
疯狂写水题找自信
题意:任何数都可以分解成2的幂次方的和(1,2,4。。。),给出一个数,问用2的幂次方有多少种表示方法
思路:dp递推,有一下公式:
当n为奇数,有dp[n] = dp[n-1];
当n为偶数,有dp[n] = dp[n-1]+dp[n/2];
证明:对于奇数n的情况,很明显就是偶数n-1的所有凑法+1即可,对于偶数的情况,我没推出来,看了大神的解释,我再重载一下:对于偶数n,假设有n1种含1的凑法和n2种不含1的凑法,很明显n1=dp[n-1],因为这些加1就一定含1,对于n2,不含1的凑发,则由2,4,8等等组成,全部除以2就是dp[n/2]的凑法,因为dp[n/2]的凑法*2一定不含1且刚好凑到n;
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<iomanip>
using namespace std;
typedef long long ll;
const long long ll_inf=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
const double pi=acos(-1.0);
const int Max=1000000+5;
const int Mod=1e9;
//cout<<setw(8)<<fixed<<setprecision(3);
//ios::sync_with_stdio(false);
int dp[Max];
int main(){
dp[0] = 0;
dp[1] = 1;
dp[2] = 2;
for (int i=3;i<Max;i++){
if (i%2==1){
dp[i] = dp[i-1]%Mod;
}else
dp[i]=(dp[i/2]+dp[i-1])%Mod;
}
int a;
scanf("%d",&a);
printf("%d\n",dp[a]);
return 0;
}
另外,这还可以用完全背包的思想来做,把1,2,4。。。看成物品,给出的n看作容量,虽然求的是装背包的方法,不过大同小异。感觉这里很有必要把完全背包的递推变形的式子拿出来推一推
用i+1种物品占j重量的最大价值等于用i种物品在背包为j-k*重量的情况下装上k个第i+1物品的价值,然后枚举k看要装多少;当k=0时后面等于 dp[i][j] ;然后把k变k+1,这样前面就变成了第一个式子,然后就可以约去多个式子变成两个式子的最大值了。
稍微变化一下就可以变成用i+1种物品占j重量的方法:
memset(dp,0,sizeof(dp));
dp[0]=1;
for(i=1;i<=n;i=i<<1){
for(j=i;j<=n;j++){
dp[j]+=dp[j-i];
dp[j]%=Mod;
}
}
cout<<dp[n]<<endl;
这里是直接优化成一维数组了,并且直接用i代替了w[i]
题意略
思路:动态规划,设dp[i][j]为变化i次,第j棵树最多能得到的苹果,注意这里的变化i次是一定要变化i次,这样奶牛的位置就是确定的且对后续答案没有影响,状态转移方程就可以写出来了
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<iomanip>
using namespace std;
typedef long long ll;
const long long ll_inf=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
const double pi=acos(-1.0);
const int Max=1000+5;
const int Mod=1e9;
//cout<<setw(8)<<fixed<<setprecision(3);
//ios::sync_with_stdio(false);
int main(){
int t,w;
cin>>t>>w;
int apple[Max];
for(int i=1;i<=t;i++){
cin>>apple[i];
}
int dp[31][Max]={0};
for(int j=1;j<=t;j++){
if(apple[j]==1)
dp[0][j]=dp[0][j-1]+1;
else
dp[0][j]=dp[0][j-1];
}
for(int i=1;i<30;i++){
dp[i][1]=1;
}
for(int i=1;i<=w;i++){//次数
for(int j=2;j<=t;j++){//第几分钟
if(apple[j]==i%2+1)
dp[i][j]=max(dp[i][j-1],dp[i-1][j-1])+1;
else
dp[i][j]=max(dp[i][j-1],dp[i-1][j-1]);
}
}
cout<<dp[w][t]<<'\n';
}
当 当前位置j可以吃到苹果时,变化i次,第j棵树最多能得到的苹果是要么变化要么不变化的第j-1棵树+1,反之当前位置不能吃到苹果的话就不+1
题意:有一群奶牛要挤奶,每只奶牛有开始时间,结束时间和挤奶量,并且每只奶牛挤奶后有一段休息时间,这段时间其他奶牛不能挤奶,问最多能挤多少奶
思路:本来想贪心的,但是每段时间有权值,还有重叠,不会贪,考虑到奶牛最多1k只,于是老实dp,先对奶牛以开始时间排序,方便dp时候的循环,设dp[i]为前i只奶牛最大挤奶量,初始值为第i只奶牛的挤奶量(后称val),状态转移方程为dp[i] = max(dp[i] ,dp[j]+i.val), j: 0→i,意思就是第j只奶牛的最大val+第i只奶牛的挤奶量,条件是j.end+r<=i.start,有点背包那味
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<iomanip>
using namespace std;
typedef long long ll;
const long long ll_inf=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
const double pi=acos(-1.0);
const int Max=1000+5;
const int Mod=1e9;
#define _for(i,n) for(int i=0;i<n;++i)
#define _rep(i,n) for(int i=1;i<=n;++i)
//cout<<setw(8)<<fixed<<setprecision(3);
//ios::sync_with_stdio(false);
int N,M,R;
int dp[Max];
struct node{
int sta,end,val;
bool operator < (const node &r) const{
return sta<r.sta;
}
}cow[Max];
int main(){
cin>>N>>M>>R;
_for(i,M){
cin>>cow[i].sta>>cow[i].end>>cow[i].val;
}
int ans=0;
sort(cow,cow+M);//开始时间排序
_for(i,M){
dp[i]=cow[i].val;
_for(j,i){
if(cow[i].sta>=cow[j].end+R)
dp[i]=max(dp[i],dp[j]+cow[i].val);
}
ans=max(ans,dp[i]);
}
cout<<ans<<'\n';
return 0;
}
题意:给出一个字符串,再给出一些字母的删去,添加所耗费的值,问把这个字符串变成回文串需要的最小花费是多少(字符串仅由给出的字母组成,可以在任何位置随意添加或删除)
思路:给出下列递推式子:dp[i][j]表示第i个字母到第j个字母变成回文串最小的花费
设给出字符串s,
当s[i]==s[j],则dp[i][j] = dp[i+1][j-1];
否则,dp[i][j] = min(dp[i+1][j] + s[i].min , dp[i][j-1] + s[j].min) ; 其中s[i].min指s[i]这个字母的删去,添加两者耗费的最小值
第一个式子很好理解,当左右两头相等,则已经是回文串,耗费与去掉左右两头的字符串变成回文串的花费相同
第二个式子比较难,首先对于串i到j,要得到回文串就要对左边或右边的字母进行删去或添加(在另外一头添加),然后加上相应的值。考虑串是从小到大进行dp的,在进行状态转移的时候只考虑小串变成回文串的花费,并不考虑小串的构成,比如长度的变化也不用管,所以对于删除和添加的操作其实无关后续,因此选择花费最小的操作即可
其他就没啥了,dp数组初始化0即可,题目有提到长度为0的字符串视为回文串
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<iomanip>
using namespace std;
typedef long long ll;
const long long ll_inf=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
const double pi=acos(-1.0);
const int Max=2000+5;
const int Mod=1e9;
#define _for(i,n) for(int i=0;i<n;++i)
#define _rep(i,n) for(int i=1;i<=n;++i)
//cout<<setw(8)<<fixed<<setprecision(3);
//ios::sync_with_stdio(false);
int dp[Max][Max]={0};
int n,m;
string s;
int a[Max];
int w[30];
int main(){
cin>>n>>m;
cin>>s;
_for(i,n){
a[i]=s[i]-'a';
}
int x,y;
char ch;
_for(i,n){
cin>>ch>>x>>y;
w[ch-'a']=min(x,y);
}
sort(al,al+n);
for(int i=m-1;i>=0;i--){
for(int j=i+1;j<m;j++){
if(a[i]==a[j])
dp[i][j]=dp[i+1][j-1];
else
dp[i][j]=min(dp[i+1][j]+w[a[i]],dp[i][j-1]+w[a[j]]);
}
}
cout<<dp[0][m-1]<<'\n';
}
优化递推关系式dp
先放一放,dp做吐了