动态规划:
(1)状态表示
(2)状态计算
背包问题
01背包
#include <iostream>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int f[N];
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++)
cin>>v[i]>>w[i];
for (int i=1;i<=n;i++)
for (int j=m;j>=v[i];j--)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[m];
}
完全背包
#include <iostream>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int f[N];
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++) cin>>v[i]>>w[i];
for (int i=1;i<=n;i++)
for (int j=v[i];j<=m;j++)
f[j]=max(f[j],f[j-v[i]]+w[i]);
cout<<f[m];
}
多重背包
二进制优化方法
#include <iostream>
using namespace std;
const int N=25000, M=2010;
int n,m;
int v[N],w[N];
int f[N];
int main()
{
cin>>n>>m;
int cnt=0;
for (int i=1;i<=n;i++)
{
int a,b,s;
cin>>a>>b>>s;
int k=1;
while(k<=s)
{
cnt++;
v[cnt]=a*k;
w[cnt]=b*k;
s-=k;
k*=2;
}
if (s>0)
{
cnt++;
v[cnt]=a*s;
w[cnt]=b*s;
}
}
n=cnt;
for (int i=1;i<=n;i++)
for (int j=m;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
cout<<f[m];
}
分组背包
#include <iostream>
using namespace std;
const int N=110;
int n,m;
int v[N][N],w[N][N],s[N];
int f[N];
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++)
{
cin>>s[i];
for (int j=0;j<s[i];j++)
cin>>v[i][j]>>w[i][j];
}
for (int i=1;i<=n;i++)
for (int j=m;j>=0;j--)
for (int k=0;k<s[i];k++)
if (v[i][k]<=j)
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
cout<<f[m];
}
线性DP
#include <iostream>
using namespace std;
const int N=510, INF=1e9;
int n;
int a[N][N],f[N][N];
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
for (int j=1;j<=i;j++)
cin>>a[i][j];
for (int i=0;i<=n;i++)
for (int j=0;j<=i+1;j++)//初始化时要多初始化一个
f[i][j]=-INF;
f[1][1]=a[1][1];
for (int i=2;i<=n;i++)
for (int j=1;j<=i;j++)
f[i][j]=max(f[i-1][j-1]+a[i][j], f[i-1][j]+a[i][j]);
int res=-INF;
for (int i=1;i<=n;i++)//遍历最后一行
{
res=max(res,f[n][i]);
}
cout<<res;
}
#include <iostream>
using namespace std;
const int N=1010;
int n;
int a[N];
int f[N];//所有以第i个数结尾的上升子序列的集合中,最大的长度
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
cin>>a[i];
for (int i=1;i<=n;i++)
{
f[i]=1;
for (int j=1;j<i;j++)
if (a[j]<a[i])
f[i]=max(f[i],f[j]+1);
}
int res=0;
for (int i=1;i<=n;i++) res=max(res,f[i]);
cout<<res;
}
#include <iostream>
#include <cstdio>
using namespace std;
const int N=1010;
int n,m;
char a[N],b[N];
int f[N][N];
//所有在第一个序列的前i个子母中出现,且在第二个序列的前j的字母中出现的子序列
//的最大值
int main()
{
scanf("%d%d",&n,&m);
scanf("%s%s",a+1,b+1);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
f[i][j]=max(f[i-1][j], f[i][j-1]);
if (a[i]==b[j]) f[i][j]=max(f[i][j], f[i-1][j-1]+1);
}
printf("%d",f[n][m]);
}
区间DP
#include <iostream>
using namespace std;
const int N=310;
int n;
int s[N];
int f[N][N];
//所有将第i堆石子到第j堆石子合并成一堆石子的合并方式中,代价的最小值
int main()
{
cin>>n;
for (int i=1;i<=n;i++)
cin>>s[i];
for (int i=1;i<=n;i++)
s[i]+=s[i-1];
for (int len=2;len<=n;len++)//区间长度从小到大枚举
for (int i=1;i+len-1<=n;i++)
{
int l=i,r=i+len-1;
f[l][r]=1e8;//初始化
for (int k=l;k<r;k++)
{
f[l][r]=min(f[l][r], f[l][k]+f[k+1][r]+s[r]-s[l-1]);
}
}
cout<<f[1][n];
}
计数类DP
整数划分
题目描述
一个正整数n可以表示成若干个正整数之和,形如:n=n1+n2+…+nk,其中n1≥n2≥…≥nk,k≥1
我们将这样的一种表示称为正整数n的一种划分。
现在给定一个正整数n,请你求出n共有多少种不同的划分方法。
输入格式
共一行,包含一个整数n。
输出格式
共一行,包含一个整数,表示总划分数量。
由于答案可能很大,输出结果请对10^9+7取模。
数据范围
1≤n≤1000
输入样例:
5
输出样例:
7
解法一、完全背包解法
#include <iostream>
using namespace std;
const int N=1010, mod=1e9+7;
int n;
int f[N];
int main()
{
cin>>n;
f[0]=1;
for (int i=1;i<=n;i++)
for (int j=i;j<=n;j++)
f[j]=(f[j]+f[j-i])%mod;
cout<<f[n];
}
解法二、朴素解法
#include <iostream>
using namespace std;
const int N=1010, mod=1e9+7;
int n;
int f[N][N];
int main()
{
cin>>n;
f[1][1]=1;
for (int i=2;i<=n;i++)
for (int j=1;j<=i;j++)
f[i][j]=(f[i-1][j-1]+f[i-j][j])%mod;
int res=0;
for (int i=1;i<=n;i++)
res=(res+f[n][i])%mod;
cout<<res;
}
数位统计DP
状态压缩DP
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N=12, M=1<<N;
int n,m;
LL f[N][M];
//f[i][j]:第i列,上一列伸出来的小方格的状态(二进制)存储在j中
bool st[M];
int main()
{
while(cin>>n>>m, n||m)
{
memset(f, 0, sizeof f);
for (int i=0;i<1<<n;i++)
{
st[i]=true;
int cnt=0;//这一段连续0的个数
for (int j=0;j<n;j++)
if (i>>j & 1)
{
if (cnt & 1) st[i]=false;//上一段有奇数个0,不存在
cnt=0;
}
else cnt++;
if (cnt & 1) st[i]=false;//最后一段
}
f[0][0]=1;
for (int i=1;i<=m;i++)
for (int j=0;j<1<<n;j++)
for (int k=0;k<1<<n;k++)//j上一列的
if ((j & k)==0 && st[j|k])
f[i][j]+=f[i-1][k];
cout<<f[m][0]<<endl;
}
}
#include <iostream>
#include <cstring>
using namespace std;
const int N=20,M=1<<N;
int n;
int w[N][N];
int f[M][N];
//f[i][j]:所有从0走到j,走过的所有点存在i当中的所有路径
//i用一个二进制数表示
//i:10011 表示第1、2、5个点走过了
int main()
{
cin>>n;
for (int i=0;i<n;i++)
for (int j=0;j<n;j++)
cin>>w[i][j];
memset(f, 0x3f, sizeof f);
f[1][0]=0;
for (int i=0;i<1<<n;i++)
for (int j=0;j<n;j++)
if (i>>j & 1)//i里面包含j,才有意义
{
for (int k=0;k<n;k++)
if ((i-(1<<j))>>k & 1)
f[i][j]=min(f[i][j], f[i-(1<<j)][k]+w[k][j]);
}
cout<<f[(1<<n)-1][n-1];
}
树形DP
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=6010;
int n;
int h[N],e[N],ne[N],idx;
int happy[N];
int f[N][2];
//f[u][0]:所有从以u为根的子树中选择,并且不选u这个点的方案(最大值)
//f[u][1]:所有从以u为根的子树中选择,并且选择u这个点的方案(最大值)
bool has_fa[N];
void add(int a, int b)
{
e[idx]=b, ne[idx]=h[a], h[a]=idx++;
}
void dfs(int u)
{
f[u][1]=happy[u];
for (int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
dfs(j);
f[u][1]+=f[j][0];
f[u][0]+=max(f[j][0], f[j][1]);
}
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&happy[i]);
memset(h,-1,sizeof h);
for (int i=0;i<n-1;i++)
{
int a,b;
scanf("%d%d",&a,&b);
add(b,a);
has_fa[a]=true;
}
int root=1;
while(has_fa[root]) root++;
dfs(root);
printf("%d",max(f[root][0],f[root][1]));
}
记忆化搜索
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=310;
int n,m;
int g[N][N];
int f[N][N];
//f[i][j]:所有从(i,j)开始滑的路径(最大值)
int dx[4]={-1,0,1,0}, dy[4]={0,-1,0,1};
int dp(int x, int y)
{
int &v=f[x][y];//简化代码
if (v!=-1) return v;
v=1;
for (int i=0;i<4;i++)
{
int a=x+dx[i], b=y+dy[i];
if (a>=1 && a<=n && b>=1 && b<=m && g[x][y]>g[a][b])
v=max(v, dp(a,b)+1);
}
return v;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
scanf("%d",&g[i][j]);
memset(f, -1, sizeof f);
int res=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
res=max(res,dp(i,j));
printf("%d",res);
}