线性动态规划
大多数题目为线性背景。
P1868 饥饿的奶牛
#include "bits/stdc++.h"
using namespace std;
struct node{
int l,r;
int sum=0;
}a[200010];
vector<int> b[3000010];
int f[3000010];
int main()
{
int n;
cin>>n;
int r=0;
for (int i=1;i<=n;i++){
cin>>a[i].l>>a[i].r;
a[i].sum=a[i].r-a[i].l+1;
b[a[i].r].push_back(a[i].l-1);
r=max(r,a[i].r);
}
for (int i=1;i<=r;i++){
f[i]=f[i-1];
for (int j=0;j<(int)b[i].size();j++){
int k=b[i][j];
f[i]=max(f[i],f[k]+i-k);
}
}
cout<<f[r]<<endl;
}
P1091 [NOIP2004 提高组] 合唱队形
分别总前后求两遍最长上升子序列。
#include "bits/stdc++.h"
using namespace std;
int a[110];
int f[110],g[110];
int main()
{
int n;
cin>>n;
for (int i=1;i<=n;i++){
cin>>a[i];
}
//最多的人满足先升后降。
for (int i=1;i<=n;i++){
for (int j=0;j<i;j++){
if(a[i]>a[j]){
f[i]=max(f[i],f[j]+1);
}
}
}
a[n+1]=0;
for (int i=n;i>=1;i--){
for (int j=n+1;j>i;j--){
if(a[i]>a[j]){
g[i]=max(g[i],g[j]+1);
}
}
}
int ans=0;
for (int i=1;i<=n;i++){
ans=max(ans,f[i]+g[i]-1);
}
cout<<n-ans;
}
P1280 尼克的任务
P1280 尼克的任务 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:设f[i] 为前1~n 的·最大休闲时间,因为正推的话,后面的份工作时间会影响到前面,所以采用倒推的方法。 状态的转移分为两种情况:一种是当这个点存在工作时间时
f[i]=max(f[i],f[i+v[i][j]]);
另一种就是在上一个的基础上加一即可。
最后输出f[1] 为答案。
#include "bits/stdc++.h"
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0);
#define all(x) x.begin(),x.end()
#define pi pair<int,int>
#define vi vector<int>
#define si set<int>
#define mi map<int,int>
#define mc map<char,int>
#define YES cout<<"Yes"<<endl;
#define NO cout<<"No"<<endl;
#define pb(x) push_back(x);
#define fi first
#define sc second
#define is insert
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return true; } return false; }
template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return true; } return false; }
const int INF =1e18;
const int mod=1000000007;
vector<int> v[30010];
int f[30010];
signed main()
{
IOS
//.........................//
int n,k;
cin>>n>>k;
for (int i=1;i<=k;i++){
int x,y;
cin>>x>>y;
v[x].push_back(y);
}
for (int i=n;i>=1;i--){
if(v[i].size()>0){
for (int j=0;j<v[i].size();j++){
f[i]=max(f[i],f[i+v[i][j]]);
}
}
else {
f[i]=f[i+1]+1;
}
}
cout<<f[1];
}
P2679 [NOIP2015 提高组] 子串
P2679 [NOIP2015 提高组] 子串 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:一个子串匹配的dp问题 ,考虑的状态有 a的前i个,b的前j个,已经匹配了t个字串了,这个0/1 字符是否使用.
因为根据提示我们可以想到一个三维的状态表达式,但当前的状态无法和之前的状态联系起来,就是不同的字符我们前后选择可能不同。所以用0/1 这一维来指示是否使用。
接下来就是想状态转移方程了:可以分为两种,使用和不使用
当a[i]==b[j] 时:
f[i][j][t][1]=f[i-1][j-1][t][1]+f[i-1][j-1][k-1][1]+f[i-1][j-1][t-1][0];
f[i][j][t][0]=f[i-1][j][t][1]+f[i-1][j][t][0];
否则:
f[i][j][t][0]=f[i-1][j][t][1]+f[i-1][j][t][0];
f[i][j][t][1]=0;
发现内存会超,使用滚动数组优化一下:
第一维只开两个内存就可以了 (因为每次更新只使用了i和i-1 ).
#include "bits/stdc++.h"
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0);
#define all(x) x.begin(),x.end()
#define pi pair<int,int>
#define vi vector<int>
#define si set<int>
#define mi map<int,int>
#define mc map<char,int>
#define YES cout<<"Yes"<<endl;
#define NO cout<<"No"<<endl;
#define pb(x) push_back(x);
#define fi first
#define sc second
#define is insert
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return true; } return false; }
template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return true; } return false; }
const int INF =1e18;
int f[2][1010][210][2];
const int mod = 1000000007 ;
signed main()
{
IOS
//.........................//
int m,n,k;
cin>>n>>m>>k;
string a,b;
cin>>a>>b;
a=" "+a;
b=" "+b;
int now=1;
f[0][0][0][0]=1;
f[1][0][0][0]=1;
for (int i=1;i<=n;i++){
for (int j=1;j<=m;j++){
for (int t=1;t<=k;t++){
if(a[i]==b[j]){
f[i%2][j][t][1]=(f[(i-1)%2][j-1][t][1]+f[(i-1)%2][j-1][t-1][1]+f[(i-1)%2][j-1][t-1][0])%mod;
f[i%2][j][t][0]=(f[(i-1)%2][j][t][1]+f[(i-1)%2][j][t][0])%mod;
}
else {
f[i%2][j][t][1]=0;
f[i%2][j][t][0]=(f[(i-1)%2][j][t][1]+f[(i-1)%2][j][t][0])%mod;
}
}
}
}
cout<<(f[n%2][m][k][1]+f[n%2][m][k][0])%mod;
}
背包动态规划
P2946 [USACO09MAR] Cow Frisbee Team S
这题限制条件是总和为f的倍数。
状态方程为:
f[i][j] //前i个物品中模数为j
状态转移方程:
dp[i][j]=(dp[i][j]+dp[i-1][j])%mod+dp[i-1][(j-a[i]+f)%f]%mod;
//分为取与不取
#include "bits/stdc++.h"
using namespace std;
#define int long long
int a[2010];
const int mod=1e8;
int dp[2010][1010];
signed main()
{
int n,f;
cin>>n>>f;
for (int i=1;i<=n;i++){
cin>>a[i];
a[i]%=f;
}
for (int i=1;i<=n;i++){
dp[i][a[i]]=1;
}
for (int i=1;i<=n;i++){
for (int j=0;j<=f-1;j++){
dp[i][j]=(dp[i][j]+dp[i-1][j]+dp[i-1][(j-a[i]+f)%f])%mod;
}
}
cout<<dp[n][0];
}
P5020 [NOIP2018 提高组] 货币系统
这题运用了集合的思想,如果a集和中的数不能被其他数组成,则b集合中应该有这个数。
反之则不该有这个数。这样的话可以保证b的种类最小。
#include "bits/stdc++.h"
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0);
#define all(x) x.begin(),x.end()
#define pi pair<int,int>
#define vi vector<int>
int f[25010];
int a[110];
void solve()
{
int n;
cin>>n;
memset(f,0,sizeof f);
for (int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+n+1);
f[0]=1;
int ans=n;
for (int i=1;i<=n;i++){
if(f[a[i]]){
ans--;
continue;
}
for (int j=a[i];j<=a[n];j++){
f[j]=f[j]|f[j-a[i]];
}
}
cout<<ans<<endl;
}
signed main()
{
IOS
int t;
cin>>t;
while(t--){
solve();
}
}
P1064 [NOIP2006 提高组] 金明的预算方案
这是一道01背包变形题。一般的01背包是两个状态,这道题是五个状态。
所以可以根据相应的方程式变形为下面的形式。
#include "bits/stdc++.h"
using namespace std;
int a[70],b[70];
int c[70][3];
int d[70][3];
int f[35010];
void solve()
{
int n,m;
cin>>m>>n;
for (int i=1;i<=n;i++){
int x,y,z;
cin>>x>>y>>z;
if(!z){
a[i]=x,b[i]=x*y;
}
else {
c[z][0]++;
c[z][c[z][0]]=x;
d[z][c[z][0]]=y*x;
}
}
for (int i=1;i<=n;i++){
for (int j=m;j>=a[i];j--){
f[j]=max(f[j],f[j-a[i]]+b[i]);
if(j>=a[i]+c[i][1]){
f[j]=max(f[j],f[j-a[i]-c[i][1]]+b[i]+d[i][1]);
}
if(j>=a[i]+c[i][2]){
f[j]=max(f[j],f[j-a[i]-c[i][2]]+b[i]+d[i][2]);
}
if(j>=a[i]+c[i][1]+c[i][2]){
f[j]=max(f[j],f[j-a[i]-c[i][1]-c[i][2]]+b[i]+d[i][2]+d[i][1]);
}
}
}
cout<<f[m];
}
int main()
{
int t;
//cin>>t;
t=1;
while(t--){
solve();
}
}
P5322 [BJOI2019] 排兵布阵
P5322 [BJOI2019] 排兵布阵 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:背包的变形,注意以下几点即可。
1.可以根据城堡类型来排序一遍,这样可以方便算出这个城堡类型可以取多少人(利用前缀的思想)
2.因为,取值的类型时严格大于两倍,所以状态方程要变为减2倍后再减一。
#include "bits/stdc++.h"
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0);
#define all(x) x.begin(),x.end()
#define pi pair<int,int>
#define vi vector<int>
#define si set<int>
#define mi map<int,int>
#define mc map<char,int>
#define YES cout<<"Yes"<<endl;
#define NO cout<<"No"<<endl;
#define pb(x) push_back(x);
#define fi first
#define sc second
#define is insert
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return true; } return false; }
template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return true; } return false; }
const int INF =1e18;
int dp[20010];
signed main()
{
IOS
//.........................//
int s,n,m;
cin>>s>>n>>m;
vector<vector<int>> a(n+1,vector<int>(s+1));
for (int i=1;i<=s;i++){
for (int j=1;j<=n;j++){
cin>>a[j][i];
}
}
for (int i=1;i<=n;i++){
sort(a[i].begin()+1,a[i].end());
}
for (int i=1;i<=n;i++){
for (int k=m;k>=0;k--){
for (int j=1;j<=s;j++){
if(k>2*a[i][j]){
dp[k]=max(dp[k],dp[k-2*a[i][j]-1]+i*j);
}
}
}
}
int ans=0;
for (int i=1;i<=m;i++){
ans=max(ans,dp[i]);
}
cout<<ans;
}
区间dp
P1880 [NOI1995] 石子合并
P1880 [NOI1995] 石子合并 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:典型的区间dp 因为是环,所以要断环为链。开两个dp 数组,一个存最大值,一个存最小数值。
#include "bits/stdc++.h"
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0);
#define all(x) x.begin(),x.end()
#define rll(x) x.rbegin(),x.rend()
#define pi pair<int,int>
#define vi vector<int>
#define si set<int>
#define mi map<int,int>
#define mc map<char,int>
#define YES cout<<"Yes"<<endl;
#define NO cout<<"No"<<endl;
#define pb(x) push_back(x);
#define fi first
#define sc second
#define is insert
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return true; } return false; }
template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return true; } return false; }
const int INF =1e10;
const int N = 210;
int f1[N][N];
int f2[N][N];
signed main()
{
IOS
//.........................//
int n;
cin>>n;
vi a(2*n+2);
vi s(2*n+2);
for (int i=1;i<=n;i++){
cin>>a[i];
a[i+n]=a[i];
}
for (int i=1;i<=2*n;i++){
s[i]=s[i-1]+a[i];
}
//长度,左边界,右边界,中间值
for (int len=2;len<=n;len++){
for (int i=1;i+len-1<=2*n;i++){
int j=i+len-1;
f1[i][j]=INF;
f2[i][j]=0;
for (int k=i;k<j;k++){
f1[i][j]=min(f1[i][j],f1[i][k]+f1[k+1][j]);
f2[i][j]=max(f2[i][j],f2[i][k]+f2[k+1][j]);
}
f1[i][j]+=s[j]-s[i-1];
f2[i][j]+=s[j]-s[i-1];
}
}
int ans1=INF,ans2=0;
for (int i=1;i<=n;i++){
ans1=min(ans1,f1[i][i+n-1]);
ans2=max(ans2,f2[i][i+n-1]);
}
cout<<ans1<<endl<<ans2;
}
P4170 [CQOI2007] 涂色
P4170 [CQOI2007] 涂色 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:我设状态方程为f[i][j] 表示从i到j要涂多少次色。然后就是区间dp的核心:从小区间推导到大区间。
状态的转移条件为i和j是否相等 如果相等
f[i][j]=min(f[i+1][j],f[i][j-1])
相当于可以免费多涂一次色。
否则:
f[i][j]=min(f[i][k]+f[k+1][j],f[i][j])//区间dp 常用的状态转移方程
#include "bits/stdc++.h"
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0);
#define all(x) x.begin(),x.end()
#define rll(x) x.rbegin(),x.rend()
#define pi pair<int,int>
#define vi vector<int>
#define si set<int>
#define mi map<int,int>
#define mc map<char,int>
#define YES cout<<"Yes"<<endl;
#define NO cout<<"No"<<endl;
#define pb(x) push_back(x);
#define fi first
#define sc second
#define is insert
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return true; } return false; }
template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return true; } return false; }
const int INF =1e10;
const int N = 55;
int f[N][N];
signed main()
{
IOS
//.........................//
string s;
cin>>s;
int n=s.size();
s=" "+s;
memset(f,0x7f,sizeof f);
for (int i=1;i<=n;i++){
f[i][i]=1;
}
for (int len=2;len<=n;len++){
for (int i=1;i+len-1<=n;i++){
int j=i+len-1;
if(s[i]==s[j]){
f[i][j]=min(f[i+1][j],f[i][j-1]);
}
else {
for (int k=i;k<j;k++){
f[i][j]=min(f[i][k]+f[k+1][j],f[i][j]);
}
}
}
}
//cout<<s.size()<<endl;
cout<<f[1][n];
}
树形动态规划
P1040 [NOIP2003 提高组] 加分二叉树
P1040 [NOIP2003 提高组] 加分二叉树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
1.区间dp
思路:给出了中序遍历,左边一定是右边的左子树。所以根节点一定在左右节点之间。
先初始化,节点的值和根就是自己。然后区间dp,如果左右节点挨在一起时,可以假设某一个子树为空,根可以默认为左边节点的根。
#include "bits/stdc++.h"
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0);
#define all(x) x.begin(),x.end()
#define pi pair<int,int>
#define vi vector<int>
#define si set<int>
#define mi map<int,int>
#define mc map<char,int>
#define YES cout<<"Yes"<<endl;
#define NO cout<<"No"<<endl;
#define pb(x) push_back(x);
#define fi first
#define sc second
#define is insert
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return true; } return false; }
template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return true; } return false; }
const int INF =1e18;
const int N = 35;
int f[N][N],root[N][N];
void print(int l,int r)
{
if(l>r){
return ;
}
cout<<root[l][r]<<" ";
if(l==r){
return ;
}
print(l,root[l][r]-1);
print(root[l][r]+1,r);
}
signed main()
{
IOS
//.........................//
int n;
cin>>n;
vi a(n+1);
for (int i=1;i<=n;i++){
cin>>a[i];
}
for (int i=1;i<=n;i++){
f[i][i]=a[i];
root[i][i]=i;
}
for (int len=2;len<=n;len++){
for (int i=1;i+len-1<=n;i++){
int j=i+len-1;
if(j==i+1){
f[i][j]=f[i][i]+f[j][j];
}
// f[i][j] = f[i + 1][j] + f[i][i];
root[i][j]=i;
/*root[i][j]=i;
*/
for (int k=i+1;k<j;k++){
int tmp=f[i][k-1]*f[k+1][j]+f[k][k];
if(tmp>f[i][j]){
f[i][j]=tmp;
root[i][j]=k;
}
}
}
}
cout<<f[1][n]<<endl;
print(1,n);
}
2.记忆化搜索
思路:使用递归来求最大值和根节点。
#include "bits/stdc++.h"
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0);
#define all(x) x.begin(),x.end()
#define pi pair<int,int>
#define vi vector<int>
#define si set<int>
#define mi map<int,int>
#define mc map<char,int>
#define YES cout<<"Yes"<<endl;
#define NO cout<<"No"<<endl;
#define pb(x) push_back(x);
#define fi first
#define sc second
#define is insert
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return true; } return false; }
template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return true; } return false; }
const int INF =1e18;
const int N = 35;
int f[N][N],root[N][N];
int dfs(int l,int r)
{
if(l>r){
return 1;
}
if(f[l][r]){
return f[l][r];
}
for (int i=l;i<=r;i++){
int tmp=dfs(l,i-1)*dfs(i+1,r)+f[i][i];
if(tmp>f[l][r]){
f[l][r]=tmp;
root[l][r]=i;
}
}
return f[l][r];
}
void print(int l,int r)
{
if(l>r){
return ;
}
cout<<root[l][r]<<" ";
if(l==r){
return ;
}
print(l,root[l][r]-1);
print(root[l][r]+1,r);
}
signed main()
{
IOS
//.........................//
int n;
cin>>n;
vi a(n+1);
for (int i=1;i<=n;i++){
cin>>a[i];
}
for (int i=1;i<=n;i++){
f[i][i]=a[i];
root[i][i]=i;
}
cout<<dfs(1,n)<<endl;
print(1,n);
}
P2585 [ZJOI2006] 三色二叉树
P2585 [ZJOI2006] 三色二叉树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:这题不同于之前的树,这题的树型结构可以采用线性的思维来写,因为给出了先序的子节点树,相当于子节点在根节点后面。所以可以直接遍历一遍建树。
状态方程: 表示在第i个节点,是否为绿色。
所以状态的转移方程有两个:
dp[i][0]=max(dp[tree[i][0]][1]+dp[tree[i][1]][0],dp[tree[i][0]][0]+dp[tree[i][1]][1]);
dp[i][1]=dp[tree[i][0]][0]+dp[tree[i][1]][0]+1;
最后只需要输出根节点的最大值最小值即可。
#include "bits/stdc++.h"
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0);
#define all(x) x.begin(),x.end()
#define pi pair<int,int>
#define vi vector<int>
#define si set<int>
#define mi map<int,int>
#define mc map<char,int>
#define YES cout<<"Yes"<<endl;
#define NO cout<<"No"<<endl;
#define pb(x) push_back(x);
#define fi first
#define sc second
#define is insert
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return true; } return false; }
template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return true; } return false; }
const int INF =1e18;
int dp[500010][2];
int tree[500010][2];
int root=1;
int cnt=0;
string s;
void dfs(int root)
{
cnt++;
if(s[root]=='0') return ;
if(s[root]=='1'){
tree[root][0]=root+1;
dfs(root+1);
}
if(s[root]=='2'){
tree[root][0]=root+1;
dfs(root+1);
tree[root][1]=cnt+1;
dfs(cnt+1);
}
}//建树过程
signed main()
{
IOS
//.........................//
cin>>s;
//先序遍历
int n=s.size();
s=" "+s;
dfs(root);
for (int i=n;i>=1;i--){
dp[i][0]=max(dp[tree[i][0]][1]+dp[tree[i][1]][0],dp[tree[i][0]][0]+dp[tree[i][1]][1]);
dp[i][1]=dp[tree[i][0]][0]+dp[tree[i][1]][0]+1;
}
cout<<max(dp[1][0],dp[1][1])<<" ";
for (int i=n;i>=1;i--){
dp[i][1]=dp[tree[i][0]][0]+dp[tree[i][1]][0]+1;
dp[i][0]=min(dp[tree[i][0]][1]+dp[tree[i][1]][0],dp[tree[i][0]][0]+dp[tree[i][1]][1]);
}
cout<<min(dp[1][0],dp[1][1]);
}