动态规划专题:线性dp、背包问题,区间

目录

方块与收纳盒

舔狗舔到最后一无所有

可爱の星空

数字三角形

花店橱窗

[NOI1998]免费馅饼

[NOIP2002]过河卒

[NOIP2008]传球游戏

「木」迷雾森林

[NOIP2004]合唱队形

[NOIP1999]拦截导弹

数学考试

小A买彩票

购物

牛牛的旅游纪念品

[NOIP2001]装箱问题

[NOIP2005]采药

[NOIP2006]开心的金明

CSL分苹果

失衡天平

[NOIP2006]金明的预算方案

队伍配置

取数游戏2

石子合并

石子合并

[NOIP2008]传纸条

[NOIP2000]方格取数

[NOIP2010]乌龟棋

[NOIP2006]能量项链

[NOIP2018]货币系统

方块与收纳盒

预处理之后很简单,已知0,1,2的方案数,那么之后每次都是i-1的方案数+i-2的方案数。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define inf 0x3f3f3f3f
#define pii pair<int,int>
const int N=100;
int dp[N];
void solve() {
	int n;
	cin>>n;
	cout<<dp[n]<<endl;
}
signed main() {
	ios::sync_with_stdio ( false );cin.tie ( nullptr );	cout.tie ( nullptr );
	int oyyo = 1;
	dp[0]=1;
	dp[1]=1;
	dp[2]=2;
	for(int i=3;i<=100;i++){
		dp[i]=dp[i-1]+dp[i-2];
	}
	cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

舔狗舔到最后一无所有

因为不能连续三天吃一样的东西,所以可以先假设第一天吃A,然后情况就是第i-1天吃A,第i-1天吃B,第i-1天吃C,其中第i-1天吃A时,第i-2天不能吃A,只能吃B和C。

所以第i天吃A的情况就是第i-2天吃B,第i-2天吃C,第i-1天吃B,第i-1天吃C。

对于第i天吃B,C的情况和吃A的情况一样。

B:第i-2天吃A,第i-2天吃C,第i-1天吃A,第i-1天吃C。

C:第i-2天吃A,第i-2天吃B,第i-1天吃A,第i-1天吃B。

所以最后第i天的情况总和为2*dp[i-1]+2*dp[i-2]

#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
#define endl '\n'
#define inf 0x3f3f3f3f
#define pii pair<int,int>
const int N=100005;
const int mod=1e9+7;
int dp[N];
void solve() {
	int n;
	cin>>n;
	cout<<dp[n]<<endl;
	
}
signed main() {
	ios::sync_with_stdio ( false );cin.tie ( nullptr );cout.tie ( nullptr );
	int oyyo = 1;
	dp[0]=1;
	dp[1]=3;
	dp[2]=9;
	for(int i=3;i<N;i++){
		dp[i]=dp[i-1]%mod*2%mod+dp[i-2]*2%mod;
		dp[i]%=mod;
	}
	cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

可爱の星空

给了你n个点,可以连成图,u和v两个小图连图的代价是  |u连通块的大小-v连通块的大小|  (绝对值),问你最终连成一个大块的最小代价是什么。

挺牛逼的题,被吓了。

他的范围会有一些很大的,所以一时间不知道怎么搞。问题的解法是单一的,对于一个数,最小代价的解法就是尽量平分成两个相同大小,奇数就是 n/2 和 n/2+1,偶数就是两个 n/2,当n==1,n==2时,都直接返回0,状态方程很好写,但当面对大数就很难堪,其实只要稍微有一些改写就好了。

如果你让这里再去递归两次就会超时,所以直接乘2就好。这个每次都除二,大胆写就好。我就是没胆量。

#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
#define endl '\n'
#define inf 0x3f3f3f3f
#define pii pair<int,int>
const int N=100005;
const int mod=1e9+7;
int answ(int n){
	if(n==1)
		return 0;
	if(n==2){
		return 0;
	}
	if(n%2==1){
		return answ(n/2)+answ(n/2+1)+1;
	}else{	
		return answ(n/2)*2;//**there。**//
	}
}
void solve() {
	int n;
	cin>>n;
	cout<<answ(n)<<endl;
}
signed main() {
	ios::sync_with_stdio ( false );cin.tie ( nullptr );cout.tie ( nullptr );
	int oyyo = 1;
	
	cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

数字三角形

照他说的做。

第一个数的公式是k=i*(i+1)/2-(i-1)

#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
#define endl '\n'
#define inf 0x3f3f3f3f
#define pii pair<int,int>
const int N=100005;
const int mod=1e9+7;

void solve() {
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		int k;
		k=i*(i+1)/2-(i-1);
		
		for(int j=1;j<=i;j++){
			printf("%4d",k+j-1);
		}
		cout<<endl;
	}
}
signed main() {
	//ios::sync_with_stdio ( false );cin.tie ( nullptr );cout.tie ( nullptr );
	int oyyo = 1;
	
	//cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

花店橱窗

这个❀首先要满足题目的要求,每个花都要按原编号顺序从左到右排列。所以第一个花最多摆到末尾位置-f+1,第 i  个花最前摆到 i 位置。

我们可以这样做。对于第 x,y 位置,它可以继承 x-1行,(x~y-1)的所有情况,所以我们的dp数组就去记录在每个位置下,他最多的美观度是多少。首先对第一行,如果当前的花 A 加上花瓶构成的美观度,小于这一行目前出现的最大值,那么我们就直接记录最大值就好,因为对于下一行(下一个要判断的花),我们如果会再考虑现在这个花 A ,当且仅当在下一行我们会走到A花之后的位置,这样才会去讨论 A 的价值,但在这个情况下,我们完全可以选择A花之前的最优情况,所以直接去记录目前为止的最大值就好。对于之后的行也是如此,如果当前花A(x,y)的价值+dp[x-1][y-1]的价值(因为dp[x-1][y-1] 就是在这个位置可以选到的最大价值了)小于这一行的上一个位置dp[x][y-1],那么就去继承他左边的位置。否则就是他自己又产生了新的最大值。

坐标的输出比较搞,我搜的。但不难。

#include<bits/stdc++.h>
using namespace std;
#define int  long long
#define endl '\n'
#define inf 0x3f3f3f3f
#define pii pair<int,int>
const int N=105;
const int mod=1e9+7;

int a[N][N];
int dp[N][N];
int d[N];
void solve() {
	
	int f,v;//一共有F中❀,V个花瓶。
	cin>>f>>v;
	
	for(int i=1;i<=f;i++){
		for(int j=1;j<=v;j++){
			cin>>a[i][j];
			dp[i][j]=-inf;//初始化dp数组,以为会存在负数,为了让dp[i]记录当前可能的最大情况就要初始化为负无穷。
		}
	}
	
	int maxn=a[1][1];
	for(int i=1;i<=v-f+1;i++){
		if(a[1][i]>maxn){
			dp[1][i]=a[1][i];
			maxn=a[1][i];
			//首先先去找第一行的最大可能情况。如果他不够大,他可以继承前面的最大情况。
		}else{
			dp[1][i]=maxn;
		}
	}
	
	for(int i=2;i<=f;i++){
		for(int j=i;j<=v-f+i;j++){//每一行只能从第i个开始选,因为要满足题目所给“左右一致”
			//之后哦每一行,他要么是继承前一个的最大值。
			dp[i][j]=max(dp[i][j-1],dp[i-1][j-1]+a[i][j]);
		}
	}
	//找到后输出最大值。
	cout<<dp[f][v]<<endl;
	//然后回溯去寻找序列。
	int ans=dp[f][v];
	int cnt=f;
	for(int i=f;i>=1;i--){
		for(int j=1;j<=v;j++){
			if(dp[i][j]==ans){
				d[cnt--]=j;//当他找到和要要寻找的数等大的。就直接传递回上一层,这样字典序最小。
				ans-=a[i][j];//找到后减去这一行的最大值位置。就是上一行所能产生的最大值了。
				break;
			}
		}
	}//出
	for(int i=1;i<=f;i++){
		cout<<d[i]<<" ";
	}
}
signed main() {
	ios::sync_with_stdio ( false );cin.tie ( nullptr );cout.tie ( nullptr );
	int oyyo = 1;
	//cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

[NOI1998]免费馅饼

[NOIP2002]过河卒

因为只能向下或向右去走,所以到一个点只有通过它左边的路或上边的路。

那么到某点的路径条数=到它左边点的条数+到它上面点的条数(前提是这两点都可到)

真正到边线上的点的路径条数应该是他的上一个点的条数(上边界就是它左边的点,左边姐就是它上边的点)而不是简单的规定为1。还有特殊的就是原点了。我们把它规定路径条数为1。

用数组记录马控制的区域表示此路不通。在循环判断到这个点后直接跳过就好。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define inf 0x3f3f3f3f
#define pii pair<int,int>

void solve(){
	int n,m,p,q;
	int a[30][30]={0};
	int map[30][30]={0};
	cin>>n>>m>>p>>q;
	map[p][q]=1;
	map[p-2][q-1]=1;
	map[p-2][q+1]=1;
	map[p+2][q+1]=1;
	map[p+2][q-1]=1;
	map[p-1][q-2]=1;
	map[p-1][q+2]=1;
	map[p+1][q+2]=1;
	map[p+1][q-2]=1;
	
	for(int i=0;i<=n;i++){
		for(int j=0;j<=m;j++){
			if(map[i][j]){
				continue;
			}else{
				if(i==0 and j==0){
					a[i][j]=1;
				}else if(i==0 and j>0){
					a[i][j]=a[i][j-1];
				}else if(j==0 and i>0){
					a[i][j]=a[i-1][j];
				}else{
					a[i][j]=a[i-1][j]+a[i][j-1];
				}
			}
		}
	}
	cout<<a[n][m]<<endl;
	
}
signed main() {
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	int t=1;
	//cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

[NOIP2008]传球游戏

每个同学可以从左或右边同学接球,即当前这一轮中间同学可以拿到球的总方法=上一轮左同学的可拿球方法+上一轮右同学的可拿球方法。

状态转移方程 dp[i][j] = dp[i-1][j-1]+dp[i-1][j+1];

题目说一开始从小蛮开始,那么一开始小蛮就得到了一个球,只有这一个方法可以得到球。

记住边界要特殊判断。就好。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define inf 0x3f3f3f3f
#define pii pair<int,int>
const int N=35;
int dp[N][N];
void solve(){
	int n,m;//N个人,传了M次后
	cin>>n>>m;
	dp[0][1]=1;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			if(j==1){
				dp[i][j]=dp[i-1][n]+dp[i-1][2];
			}else if(j==n){
				dp[i][j]=dp[i-1][j-1]+dp[i-1][1];
			}else{
				dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];
			}
		}
	}
	cout<<dp[m][1]<<endl;
}
signed main() {
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	int t=1;
	//cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

「木」迷雾森林

刚开始在左下方,要求只能向上或向右走,走到右上方,有1的地方不能走,求方案数对2333取模。

我们把问题分解成加法问题,开一个数组s,s【i,j】表示从左下走到i,j这个点的方案数是多少,当前的点i,j是由下方【i+1,j】和左边【i,j-1】的方案数相加,而【n,1】的方案数是1,我们遇到1的时候直接令这个点方案数为0就可以了,从下往上dp,最后输出s【1,n】%2333即可。

dp [ i] [ j ] = dp [ i - 1 ] [ j ] + d p [ i ] [ j - 1 ]

#include<bits/stdc++.h>
using namespace std;

#define endl '\n'
#define inf 0x3f3f3f3f
#define pii pair<int,int>
const int N=5005;
const int mod=1e9+7;

int a[3005][3005];
int s[3005][3005];

template<class T>inline void read(T &res)
{
	char c;T flag=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
	while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
}

void solve() {
	int n,m;
	memset(s,0,sizeof s);
	read(n),read(m);
	s[n][1]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			read(a[i][j]);
		}
	}
	for(int i=n;i>=1;i--){
		for(int j=1;j<=m;j++){
			if(i==n and j==1){
				continue;
			}
			if(a[i][j]==1){
				s[i][j]=0;
				continue;
			}
			s[i][j]=s[i+1][j]%2333+s[i][j-1]%2333;
			s[i][j]%=2333;
		}
	}

	cout<<s[1][m]<<endl;
}
signed main() {
	ios::sync_with_stdio ( false );cin.tie ( nullptr );cout.tie ( nullptr );
	int oyyo = 1;
	//cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

[NOIP2004]合唱队形

本题是要求出从 第一个人到第i个最高的人的最长上升子序列  以及从第i个人到第n个人的最长下降子序列(其实就是第n个人反向到第i个人的最长上升子序列)

建立二维数组dp[105][2]。

dp[i][0]代表以第i个人为结尾的最长上升子序列长度。(整个合唱队形没有下降)

dp[i][1]代表以第i个人为结尾的最长合唱队形,但至少有一个人的身高呈下降趋势(合唱队形有下降)

显然,max(a[i][0],a[i][1])代表前i个人的最长合唱队形。

#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
#define endl '\n'
#define inf 0x3f3f3f3f
#define pii pair<int,int>
const int N=100005;
const int mod=1e9+7;
int dp[105][2];
void solve() {
	int n;
	cin>>n;
	vector<int>a(n+1);
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		dp[i][0]=1;
		for(int j=1;j<i;j++){
			if(a[i]>a[j]){
				dp[i][0]=max(dp[i][0],dp[j][0]+1);
			}
		}
	}
	for(int i=1;i<=n;i++){
		dp[i][1]=1;
		for(int j=1;j<i;j++){
			if(a[i]<a[j]){
				dp[i][1]=max(dp[i][1],max(dp[j][0],dp[j][1])+1);
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		ans=max(ans,max(dp[i][0],dp[i][1]));
	}
	cout<<n-ans<<endl;
}
signed main() {
	ios::sync_with_stdio ( false );cin.tie ( nullptr );cout.tie ( nullptr );
	int oyyo = 1;
	//cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

[NOIP1999]拦截导弹

真的没搞懂。。。。。。

#include<bits/stdc++.h>
using namespace std;
#define int  long long
#define endl '\n'
#define inf 0x3f3f3f3f
#define pii pair<int,int>
const int N=100005;
const int mod=1e9+7;

int f[N];
int a[N];
int n,t;
void solve() {

	while(cin>>a[++n]);
	n--;
	t=0;
	memset(f,0,sizeof(f));f[0]=inf;
	for(int i=1;i<=n;i++){
		int l=0,r=t+1;
		while(r-l>1){
			int m=l+(r-l)/2;
			if(f[m]>=a[i]){
				l=m;
			}else{
				r=m;
			}
		}
		int x=l+1;
		if(x>t){
			t=x;
		}
		f[x]=a[i];
	}
	cout<<t<<endl;
	t=0,memset(f,0,sizeof(f));
	f[0]=0;
	for(int i=1;i<=n;i++){
		int l=0,r=t+1;
		while(r-l>1){
			int m=l+(r-l)/2;
			if(f[m]<a[i]){
				l=m;
			}else{
				r=m;
			}
		}
		int x=l+1;
		if(x>t){
			t=x;
		}
		f[x]=a[i];
	}
	cout<<t<<endl;
	
}
signed main() {
	ios::sync_with_stdio ( false );cin.tie ( nullptr );cout.tie ( nullptr );
	int oyyo = 1;
	//cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

数学考试

先求前缀和,因为两个区间不相交,所以找一个基准点,找到该基准点左侧的sum最大的的区间(可以将该基准点视为在左区间),再在基准点右侧找到sum最大的区间,最后相加。

让每个数都做基准点,找到它们各自的左区间sum最大
  1. zuo[i] 为以 i 做基准点,左侧的sum最大区间。
  2. 因为要求的的区间长度为k,所以 i 要从 k开始,如果从 k 以前的数开始的话,区间长度根本都不满足题中要求的长度k。
让每个数都做基准点,找到它们各自的右区间sum最大
  1. you[i] 为以 i 做基准点,右侧的sum最大区间。
  2. i要从从右数k个数开始,即n - k + 1
#include<bits/stdc++.h>
using namespace std;
#define int  long long
#define endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f3f
#define pii pair<int,int>
const int mod=1e9+7;
const int N=200005;
int b[N];
int you[N];
int a[N];

void solve() {
	int n,k;
	cin>>n>>k;
	memset(a,0,sizeof(a));
	memset(you,-1e4,sizeof(you));
	memset(b,0,sizeof(b));

	for(int i=1;i<=n;i++){
		cin>>a[i];
	}

	for(int i=1;i<=n;i++){
		b[i]=b[i-1]+a[i];
	}

	for(int i=n-k+1;i>=1;i--){
		you[i]=max(you[i+1],b[i+k-1]-b[i-1]);
	}
	int maxn=-1e18;
	for(int i=k;i<=n-k;i++){
		maxn=max(maxn,b[i]-b[i-k]+you[i+1]);
	}
	cout<<maxn<<endl;

}
signed main() {
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	int oyyo=1;
	cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

小A买彩票

根据题目先给出概率公式:不亏本的数量/总数量。
因为每买一张彩票有4种方法,那么买n张彩票就有4的n次方种方法。
因为每张票3元,所以不亏本的金额范围只能为3n——4n

#include<bits/stdc++.h>
using namespace std;
#define int  long long
#define endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f3f3f
#define pii pair<int,int>
const int mod=1e9+7;
const int N=200005;

int dp[35][200];

int gcd(int a,int b){
	return b==0?a:gcd(b,a%b); 
}

void solve() {
	int n;
	cin>>n;
	int ans=0;
	dp[0][0]=1;
	
    for(int i=1;i<=n;i++){
		for(int j=i;j<=4*i;j++){
			for(int k=1;k<=4;k++){
				if(j>=k){
					dp[i][j]+=dp[i-1][j-k];
				}
			}
		}
	}

	for(int i=3*n;i<=4*n;i++){
		ans+=dp[n][i];
	}
	
	int sum=pow(4,n);
	int gbs=gcd(ans,sum);
	cout<<ans/gbs<<"/"<<sum/gbs<<endl;
	
}
signed main() {
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	int oyyo=1;
	//cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

购物

牛牛的旅游纪念品

这个题如果没有对位置的限制,就可以贪心取前m大的数即可。
但本题的关键在于买的m个物品中任意两个的位置差都大于等于k。那么贪心的方法显而易见行不通,很容易联想到用动态规划。
由题可知本题有两个状态,n个物品以及取走m个物品,并且对物品的位置有着限制,那么我们可以定义
dp[i][j]:前j个物品取i个的欢迎程度
dp[i][j]无非由两种状态转移而来

   1.  没有买第j个物品,那么dp[i][j]=dp[i][j-1]。
   2. 买了第j个物品,那么此时dp[i][j]=dp[i-1][j-k]+a[j]。

很容易得到状态转移 方程:dp[i][j]=max(dp[i][j-1],dp[i-1][j-k]+a[j])。
初始状态就是在取第一个时,dp[1][j]=max(dp[1][j-1],a[j])。

#include<bits/stdc++.h>
using namespace std;
#define int  long long
#define endl '\n'
#define inf1 0x3f3f3f3f
#define inf2 0x3f3f3f3f3f3f3f3f3f3f
#define pii pair<int,int>
const int mod=1e9+7;
const int N=25;
int dp[105][10005];
void solve() {
	memset(dp,-inf1,sizeof(dp));
	int n,m,k;
	cin>>n>>m>>k;
	vector<int>a(n+1);
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	
	for(int i=1;i<=n;i++){
		dp[1][i]=max(dp[1][i-1],a[i]);
	}
	for(int i=2;i<=m;i++){
		for(int j=k+1;j<=n;j++){
			dp[i][j]=max(dp[i][j-1],dp[i-1][j-k]+a[j]);
		}
	}
	cout<<dp[m][n]<<endl;
}
signed main() {
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	int oyyo=1;
	//cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

[NOIP2001]装箱问题

因为每个物体,都有装与不装两种选择,所以我们得到状态转移方程:
f[j]=max(f[j],f[j-w[i]]+w[i]);

f[j] 为:当总容量为 j 时,不放第 i 件物品,所能装的最大体积。
f[j-w[i]]+w[i] 为:当总容量为 j 时,放了第 i 件物品后,所能装的最大体积。(即 j减去第 i 件物品体积 的容量能装的最大体积+第 i 件物品的体积。w[i] 为第 i 件物品体积)

#include<bits/stdc++.h>
using namespace std;
#define int  long long
#define endl '\n'
#define inf 0x3f3f3f3f
#define pii pair<int,int>
const int N=100005;
const int mod=1e9+7;

int dp[20005];
int a[35];
void solve() {
	int v;
	cin>>v;
	int n;
	cin>>n;
	
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	
	for(int i=1;i<=n;i++){
		for(int j=v;j>=a[i];j--){
			dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
		}
	}
	
	cout<<v-dp[v]<<endl;
}
signed main() {
	ios::sync_with_stdio ( false );cin.tie ( nullptr );cout.tie ( nullptr );
	int oyyo = 1;
	//cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

[NOIP2005]采药

dp[i][j]=max(dp[i-1][j-a[i].t]+a[i].v,dp[i-1][j]);

#include<bits/stdc++.h>
#define endl '\n'
#define mk make_pair
//#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 3050;
const int mod = 2333;


struct cao{
	int t,v;
};

int dp[105][1005];

void sovle(){
	int t,m;
	cin>>t>>m;
	
	vector<cao>a(m+1);
	
	for(int i=1;i<=m;i++){
		cin>>a[i].t>>a[i].v;
	}
	
	for(int i=1;i<=m;i++){
		for(int j=t;j>=0;j--){
			if(j>=a[i].t){
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-a[i].t]+a[i].v);
			}else{
				dp[i][j]=dp[i-1][j];
            }
		}
	}
	cout<<dp[m][t]<<endl;
	
}

int main()
{    
	//ios::sync_with_stdio(false), cin.tie(0),cout.tie(0); 
	int t = 1;
	//cin>>t;
	while (t--){
		sovle();
	}
	
	return 0;
}

//template<class T>inline void read(T &res)
//{
//	char c;T flag=1;
//	while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
//	while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
//}

[NOIP2006]开心的金明

定义一个二维数组dp,dp[i][j]表示买到第i个商品(不一定要全买),最多花j元钱。

如果当前的钱可以买就更新状态,否则记录上一次状态。

#include<bits/stdc++.h>
using namespace std;
#define int  long long
#define endl '\n'
#define inf1 0x3f3f3f3f
#define inf2 0x3f3f3f3f3f3f3f3f3f3f
#define pii pair<int,int>
const int mod=1e9+7;
const int N=25;

struct wu{
	int v;
	int cost;
};

int dp[30][30005];

void solve() {
	
	int n,m;
	cin>>n>>m;
	//总钱数,希望购买的物品的个数。
	vector<wu>a(m+1);
	for(int i=1;i<=m;i++){
		cin>>a[i].cost>>a[i].v;
	}
	
	
	for(int i=1;i<=m;i++){
		for(int j=0;j<=n;j++){
			dp[i][j]=dp[i-1][j];
			if(j>=a[i].cost){
				dp[i][j]=max(dp[i][j],dp[i-1][j-a[i].cost]+a[i].cost*a[i].v);
			}
		}
	}
	cout<<dp[m][n]<<endl;
}
signed main() {
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	int oyyo=1;
	//cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

CSL分苹果

重量即价值,背包最大容量就是总容量的一半

#include<bits/stdc++.h>
using namespace std;
#define int  long long
#define endl '\n'
#define inf1 0x3f3f3f3f
#define inf2 0x3f3f3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define PI acos(-1.0)
const int mod=1e9+7;
const int N=25;
int dp[10005];
void solve() {
	int n;
	cin>>n;
	vector<int>a(n+1);
	int sum=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum+=a[i];
	}
	
	for(int i=1;i<=n;i++){
	    for(int j=sum/2;j>=a[i];j--){
			dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
		}
	}
	
	cout<<dp[sum/2]<<" "<<sum-dp[sum/2]<<endl;
	
}
signed main() {
	ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
	int oyyo=1;
	//cin >> oyyo;
	while ( oyyo-- ) {
		solve();
	}
	return 0;
}

失衡天平

对于每个物品我们都要做出选择:放在天平的左边,放在天平的右边,不选择。
那么我们就可以去找递推式:
dp[i][j]表示前i样物品进行选择后,此时天平两端的重量差为j的时候,最大质量是多少,我们定义这个质量差为 左减右,那么左边放物品质量差增大,右边放质量差变小
(a[i]表示第i件物品的质量)
如果我们选择第i件物品,放在天平的左边,那么dp[i][j]就是从dp[ i -1 ] [ j - a [ i ] ]来的(将第i件物品选择前,质量差为j-a[i])转移过来
同理:如果放在天平的右边,那么dp[i][j]就是dp[i-1][j+a[i]]转移而来的
如果不放,那就是dp[i-1][j]直接转移过来
我们要找到最大的情况,就是上面三个取最大值
dp[i][j] = max(dp [ i -1 ] [ j ] , d p [ i - 1 ][ j - a [ i ] ] + a [ i ] ,d p [ i - 1 ] [j +a [ i] ] + a [ i ] )
因为题目有限定这个质量差的上限,所以最后我们求出这个范围内的最大质量即可
max(sum,dp[n][i])

#include<bits/stdc++.h>
#define endl '\n'
#define mk make_pair
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5+7;
const int mod=998244353;
const int inf=LLONG_MAX;
using i128=__int128;
int dp[105][11000];
void sovle(){
	
	memset(dp,-0x3f,sizeof(dp));
	int n,m;
	cin>>n>>m;
	vector<int>a(n+1);
	int sum=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum+=a[i];
	}
	
	dp[0][0]=0;
	
	for(int i=1;i<=n;i++){
		for(int j=0;j<=sum;j++){
			dp[i][j]=max(dp[i-1][j],max(dp[i-1][abs(j-a[i])]+a[i],dp[i-1][j+a[i]]+a[i]));
			//初始化为负无穷,因为初始状态f[i][j]的值不一定为0,而应该是一个不存在的值.所以应该是负无穷
		}
	}	
	
	int max1=0;
	for(int i=0;i<=m;i++){
		max1=max(dp[n][i],max1);
	}
	
	cout<<max1<<endl;	
}	
	
signed main()
{	
	ios::sync_with_stdio(false), cin.tie(0),cout.tie(0); 
	int t = 1;
	//cin>>t;
	while (t--){
		sovle();
	}
	
	return 0;
}	

[NOIP2006]金明的预算方案

队伍配置

取数游戏2

石子合并

石子合并

[NOIP2008]传纸条

[NOIP2000]方格取数

[NOIP2010]乌龟棋

[NOIP2006]能量项链

[NOIP2018]货币系统

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值