动态规划专题训练

线性动态规划

大多数题目为线性背景。

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)

思路:这题不同于之前的树,这题的树型结构可以采用线性的思维来写,因为给出了先序的子节点树,相当于子节点在根节点后面。所以可以直接遍历一遍建树。

状态方程:    dp[i][j]  表示在第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]);
	
	
	
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值