动态规划
动态规划DP(Dynamic Programming),通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠的子问题和求优解的问题
在资源型动态规划中有01背包、完全背包、多重背包等类型
博主学习更多dping
01背包
背包有固定容量,判断取不取物体(物体只有一个)以达到最大的价值
v
a
l
u
e
[
i
]
[
j
]
=
m
a
x
(
v
a
l
u
e
[
i
]
[
j
]
,
v
a
l
u
e
[
i
−
1
]
[
j
−
u
s
e
d
[
j
]
]
+
v
a
l
[
j
]
)
value[i][j]=max(value[i][j],value[i-1][j-used[j]]+val[j])
value[i][j]=max(value[i][j],value[i−1][j−used[j]]+val[j])
洛谷 P1048 [NOIP2005 普及组] 采药
#include<bits/stdc++.h>
using namespace std;
#define foo(i,a,b) for(int i=a;i<b;i++)
const int N=130;
int n,m,f[1005];
struct herb
{
int time,value;
}h[N];
signed main()
{
cin>>n>>m;
foo(i,1,m+1)
{
cin>>h[i].time>>h[i].value;
}
foo(i,1,m+1)
{
for(int j=n;j>=1;j--)
{
if(j>=h[i].time) f[j]=max(f[j-h[i].time]+h[i].value,f[j]);//二维转一维要看数据怎么替换的
else f[j]=f[j];
}
}
cout<<f[n];
return 0;
}
完全背包
背包有固定容量,判断取不取物体(物体有无限个)以达到最大的价值
f
i
r
s
t
:
v
a
l
u
e
[
i
]
[
j
]
=
m
a
x
(
v
a
l
u
e
[
i
]
[
j
]
,
v
a
l
u
e
[
i
−
1
]
[
j
−
u
s
e
d
[
j
]
]
+
v
a
l
[
j
]
)
s
e
c
o
n
d
:
v
a
l
u
e
[
i
]
[
j
]
=
m
a
x
(
v
a
l
u
e
[
i
]
[
j
]
,
v
a
l
u
e
[
i
]
[
j
−
u
s
e
d
[
j
]
]
+
v
a
l
[
j
]
)
first:value[i][j]=max(value[i][j],value[i-1][j-used[j]]+val[j])\\ second:value[i][j]=max(value[i][j],value[i][j-used[j]]+val[j])
first:value[i][j]=max(value[i][j],value[i−1][j−used[j]]+val[j])second:value[i][j]=max(value[i][j],value[i][j−used[j]]+val[j])
洛谷 P1616 疯狂的采药
#include<bits/stdc++.h>
using namespace std;
#define foo(i,a,b) for(int i=a;i<b;i++)
const signed N=1e7+10,M=1e4+10;
int t,m,f[N];
struct herb
{
int time,value;
}h[M];
signed main()
{
cin>>t>>m;
foo(i,1,m+1)
{
cin>>h[i].time>>h[i].value;
}
foo(i,1,m+1)
{
foo(j,h[i].time,t+1)
{
f[j]=max(f[j],f[j-h[i].time]+h[i].value);//顺着来正好实现功能
}
}
cout<<f[t];
return 0;
}
多重背包
背包有固定容量,判断取不取物体(物体有有限个)以达到最大的价值,有两种思路,一种是二进制拆分、另一种是单调队列优化
二进制拆分就是把多重背包用完全二进制的思想拆成01背包
洛谷 P1776 宝物筛选
#include<bits/stdc++.h>
using namespace std;
#define foo(i,a,b) for(int i=a;i<b;i++)
#define fio(i,a,b) for(int i=a;i>b;i--)
const int N=1e5+10;
int n,W,f[N],v[N],w[N];
signed main()
{
cin>>n>>W;
int cnt,i=1,isq=1,mark,temp,add;
while(i<=n)
{
cin>>v[isq]>>w[isq]>>cnt;
temp=cnt;
mark=1,add=0;
while(temp>=mark)//二进制拆分
{
temp-=mark;
v[isq+add]=v[isq]*mark;
w[isq+add]=w[isq]*mark;
mark*=2;
add++;
}
if(temp!=0)
{
v[isq+add]=v[isq]*temp;
w[isq+add]=w[isq]*temp;
add++;
}
isq=isq+add;
i++;
}
n=isq-1;
foo(ii,1,n+1)//01背包
{
fio(j,W,w[ii]-1)
{
f[j]=max(f[j],f[j-w[ii]]+v[ii]);
}
}
cout<<f[W];
return 0;
}
单调队列优化
洛谷 P1776 宝物筛选
#include<bits/stdc++.h>
#include<bits/stdc++.h>
using namespace std;
const int N=5e4;
int n,W,v,w,m;//价值、重量、个数
int dp[N],q1[N],q2[N],head,tail,ans;
int main()
{
cin>>n>>W;
for(int i=0;i<n;i++)
{
cin>>v>>w>>m;
if(w==0)
{
ans+=v*m;
continue;
}
int group=min(W/w,m);
for(int d=0;d<w;d++)
{
head=0,tail=0;
for(int j=0;j*w+d<=W;j++)
{
while(head<tail&&dp[d+j*w]-j*v>=q2[tail-1])
{
tail--;
}
q1[tail]=j;
q2[tail]=dp[d+j*w]-j*v;
tail++;
while(head<tail&&q1[head]<j-group)
{
head++;
}
dp[d+j*w]=max(dp[d+j*w],q2[head]+j*v);
}
}
}
cout<<ans+dp[W];
return 0;
}
二维费用背包
一样的思路,只是多加一个维度选择
P1855 榨取kkksc03
#include<bits/stdc++.h>
using namespace std;
int n,m,t,dp[210][210];
int main()
{
cin>>n>>m>>t;
int tempm,tempt;
for(int i=0;i<n;i++)
{
cin>>tempm>>tempt;
for(int mm=m;mm>=tempm;mm--)
{
for(int tt=t;tt>=tempt;tt--)
{
dp[mm][tt]=max(dp[mm][tt],dp[mm-tempm][tt-tempt]+1);
}
}
}
cout<<dp[m][t];
return 0;
}
分组背包
把组作为最外循环,组中的元素作为最内侧循环,保证了只有一个的插入情况,注意理清楚每一组之间的数量
P1757 通天之分组背包
#include<bits/stdc++.h>
using namespace std;
int n,m,dp[1500];
struct things
{
int weight;
int value;
int group;
}t[1500];
bool cmp(things a,things b)
{
return a.group<b.group;
}
int main()
{
cin>>m>>n;
if(m==0)//特判m=0的情况
{
cout<<"0";
exit(0);
}
for(int i=0;i<n;i++)
{
cin>>t[i].weight>>t[i].value>>t[i].group;
}
bool flag;
sort(t,t+n,cmp);
t[n].weight=20000;t[n].value=0;t[n].group=20000;
for(int p=0;p<n;)
{
//cout<<"p=外层"<<p<<endl;
int markp=p;
for(int j=m;j>0;j--)
{
p=markp;
flag=true;
for(;flag||t[p].group==t[p+1].group;p++)
{
if(flag==false) break;
//cout<<"flag=" <<flag<<endl;
//cout<<"p="<<p<<endl;
if(j>=t[p].weight) dp[j]=max(dp[j],dp[j-t[p].weight]+t[p].value);
if(t[p].group!=t[p+1].group) flag=false;
}
}
}
cout<<dp[m];
return 0;
}
有依赖的背包
一般思路:如果背包的附件比较少,可以枚举出来,可以转化为01背包类似的想法做,但是依赖的个数渐进是指数级的
P1064 [NOIP2006 提高组] 金明的预算方案
#include<bits/stdc++.h>
using namespace std;
const int maxn=4e4;
int n,m;
int v,p,q;
int main_item_w[maxn];
int main_item_c[maxn];
int annex_item_w[maxn][3];//这种分组的对应方式很清爽
int annex_item_c[maxn][3];
int f[maxn];
int main(){
cin >> n >> m;
for (int i=1;i<=m;i++){
cin >> v >> p >> q;
if(!q)
{
main_item_w[i] = v;
main_item_c[i] = v * p;
}
else
{
annex_item_w[q][0]++;
annex_item_w[q][annex_item_w[q][0]] = v;
annex_item_c[q][annex_item_w[q][0]] = v * p;
}
}
for (int i=1;i<=m;i++)
{
for (int j=n;main_item_w[i]!=0 && j>=main_item_w[i];j--){
f[j] = max(f[j],f[j-main_item_w[i]]+main_item_c[i]);
if (j >= main_item_w[i] + annex_item_w[i][1])
f[j] = max(f[j],f[ j - main_item_w[i] - annex_item_w[i][1] ] + main_item_c[i] + annex_item_c[i][1]);
if (j >= main_item_w[i] + annex_item_w[i][2])
f[j] = max(f[j],f[ j - main_item_w[i] - annex_item_w[i][2] ] + main_item_c[i] + annex_item_c[i][2]);
if (j >= main_item_w[i] + annex_item_w[i][1] + annex_item_w[i][2])
f[j] = max(f[j],f[ j - main_item_w[i] - annex_item_w[i][1] - annex_item_w[i][2] ] + main_item_c[i] + annex_item_c[i][1] + annex_item_c[i][2]);
}
}
cout << f[n];
return 0;
}
背包问题方案总数
把状态转移方程的max变成sum即可
最长上升子序列
最长上升子序列(LIS),子序列是字符串中不一定连续但先后顺序一致的n个字符,而子串是字符串中连续的n个字符
解决方法有O(n^2)的DP,O(nlogn)的二分+贪心法,以及O(nlogn)的树状数组优化的DP
B3637 最长上升子序列
//O(n^2)的DP法
#include<bits/stdc++.h>
using namespace std;
int a[6000],dp[6000];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
dp[i]=1;
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[j]<a[i])
{
dp[i]=max(dp[i],dp[j]+1);
}
}
}
int maxn=0;
for(int i=1;i<=n;i++)
{
//cout<<dp[i]<<endl;
maxn=max(maxn,dp[i]);
}
cout<<maxn;
return 0;
}
最长公共子序列
最长公共子序列(LCS),朴素算法是O(n^2)存储空间和时间可能会卡,改良算法O(nlogn)
P1439 【模板】最长公共子序列
//O(n^2)算法
#include<bits/stdc++.h>
using namespace std;
const int N=5e3;
int n,a[N],b[N],LCS[N][N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(a[i]==b[j]) LCS[i][j]=LCS[i-1][j-1]+1;
else LCS[i][j]=max(LCS[i-1][j],LCS[i][j-1]);
}
}
cout<<LCS[n][bvn];
return 0;
}
限制条件转化为背包的动态规划
重点是求转移方程
P1077 [NOIP2012 普及组] 摆花
#include<bits/stdc++.h>
using namespace std;
int n,m,dp[110],mod=1000007;
int main()
{
cin>>n>>m;
int k;
dp[0]=1;
for(int i=1;i<=n;i++)
{
cin>>k;
for(int j=m;j>=0;j--)
{
for(int t=1;t<=k&&j>=t;t++)
{
dp[j]=(dp[j-t]+dp[j])%mod;
}
}
}
cout<<dp[m];
return 0;
}
树形动态规划
主要的实现方式是dfs,在dfs中dp,主要的实现方式是加一维记录0/1
P1352 没有上司的舞会
#include<bits/stdc++.h>
using namespace std;
const int N=6e3+10;
struct Edge
{
int from,to;
Edge(int a,int b)
{
from=a;
to=b;
}
};
vector<Edge> e[N];//下属
int in[N],out[N],dp[N][2],happy[N],n,done[N];//dp0不取,1取
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>happy[i];
}
int head=1;
for(int i=1;i<n;i++)
{
int a,b;
cin>>a>>b;
e[a].push_back(Edge(a,b));
out[b]++;
in[a]++;
}
for(int i=1;i<=n;i++)
{
if(in[i]==0)
{
head=i;
break;
}
}
while(out[head]>0)
{
//cout<<out[head]<<" out[head="<<endl;
for(int i=1;i<=n;i++)
{
if(out[i]==0&&done[i]==false&&i!=head)
{
dp[i][1]+=happy[i];
//cout<<"i="<<i<<" dp[i][0]="<<dp[i][0]<<" dp[i][1]="<<dp[i][1]<<endl;
done[i]=true;
for(int j=0;j<e[i].size();j++)
{
int y=e[i][j].to;
dp[y][0]+=max(dp[i][0],dp[i][1]);
dp[y][1]+=dp[i][0];
out[y]--;
}
}
}
}
//cout<<"head="<<head<<" 0="<<dp[head][0]<<" 1="<<dp[head][1]<<endl;
cout<<max(dp[head][0],dp[head][1]+happy[head]);
return 0;
}