二重限制的01背包 最优解及其最优解的方案数

本文探讨了一个背包问题的变种,即在给定的时间和金钱限制下,选择若干物品以最大化总价值,并同时计算达到此最大价值的所有可能方案数量。文章提供了两种解题思路:正向DP和反向DP,详细解释了状态转移方程及代码实现。

http://49.235.120.161/problem/10031

题意:N个物品  选每个物品i  将用时si+di   花费vi元   获得价值为1

问N个物品 总时间为T  钱包有M元  获得的最大价值Ans与Ans的方案数

N<=1000 T,M<=100

正着做: 

ll dp[1005][105][105]; 前i个物品 花时间至多j,花钱至多k的最大价值
ll dp2[1005][105][105];前i个物品 花时间至多j,花钱至多k的最大价值的方案数

注意dp2计算方案数是要初始化的

dp转移就是01背包嘛,就是选/不选的状态都枚举,哪个大选哪个

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
int N,T,M;
ll si[1005];
ll di[1005];
ll vi[1005]; 
ll dp[1005][105][105];//i 
ll dp2[1005][105][105];//

int main(){
	cin>>N>>T>>M;
	for(int i=1;i<=N;i++)cin>>si[i]>>di[i]>>vi[i];
	memset(dp,0,sizeof(dp));
	memset(dp2,0,sizeof(dp2));
	for(int j=0;j<=M;j++){
		for(int k=0;k<=T;k++){
			dp2[0][j][k]=1;	
		}
	}
	for(int i=1;i<=N;i++){
		for(int j=0;j<=M;j++){
			for(int k=0;k<=T;k++){
				if(j>=vi[i]&&k>=si[i]+di[i]){				
					if(dp[i-1][j][k]<dp[i-1][j-vi[i]][k-si[i]-di[i]]+1){
						dp[i][j][k]=dp[i-1][j-vi[i]][k-si[i]-di[i]]+1;
						dp2[i][j][k]=dp2[i-1][j-vi[i]][k-si[i]-di[i]]%mod;
					}
					if(dp[i-1][j][k]==dp[i-1][j-vi[i]][k-si[i]-di[i]]+1){
						dp[i][j][k]=dp[i-1][j][k];
						dp2[i][j][k]=(dp2[i-1][j-vi[i]][k-si[i]-di[i]] +dp2[i-1][j][k])%mod;
					}
					
					if(dp[i-1][j][k]>dp[i-1][j-vi[i]][k-si[i]-di[i]]+1){
						dp[i][j][k]=dp[i-1][j][k];
						dp2[i][j][k]=dp2[i-1][j][k]%mod;
					}
				}
				else{
					dp[i][j][k]=dp[i-1][j][k];
					dp2[i][j][k]=dp2[i-1][j][k];
				}		
			}
		}
	} 
	ll ans1=dp[N][M][T],ans2=dp2[N][M][T];

	cout<<ans1<<" "<<ans2%mod<<endl;
}

反着做:(滚动)但dp2的定义不同了,为恰好使用(前面是至多),dp1不变

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
const int MOD=1e9+7;
int n,m,t,s[N],d[N],v[N];
int dp1[110][110],dp2[110][110];

int main()
{
	freopen("4.in","r",stdin);
	//freopen("7.out","w",stdout);
	cin>>n>>t>>m;
	for (int i=1;i<=n;i++) scanf("%d%d%d",&s[i],&d[i],&v[i]);
	for (int i=1;i<=n;i++) s[i]+=d[i];
	
	memset(dp1,-0x3f,sizeof(dp1));
	memset(dp2,-0x3f,sizeof(dp2));
	dp1[0][0]=0; dp2[0][0]=1;
	for (int i=1;i<=n;i++)
		for (int j=t;j;j--)
			for (int k=m;k;k--) {
				if (j<s[i] || k<v[i]) continue;
				if (dp1[j-s[i]][k-v[i]]+1>dp1[j][k]) {
					dp1[j][k]=dp1[j-s[i]][k-v[i]]+1;
					dp2[j][k]=dp2[j-s[i]][k-v[i]];
				} else if (dp1[j-s[i]][k-v[i]]+1==dp1[j][k]) {
					dp2[j][k]+=dp2[j-s[i]][k-v[i]];
					dp2[j][k]%=MOD;
				}
			}
	int ans1=0,ans2=0;
	for (int j=0;j<=t;j++) for (int k=0;k<=m;k++)
		if (dp1[j][k]>ans1) ans1=dp1[j][k],ans2=dp2[j][k];
		else if (dp1[j][k]==ans1) ans2=(ans2+dp2[j][k])%MOD;
	cout<<ans1<<" "<<ans2<<" "<<dp1[t][m]<<" "<<dp2[t][m]<<endl;			  
	return 0; 
}

 

<think>我们正在讨论二重指针的解引用。根据引用[1]和引用[2]中的例子,我们可以理解二重指针解引用的含义。 引用[1]指出:将二重指针进行一次解引用就变成了一重指针。例如,有一个二重指针A,那么(*A)就是一个一重指针。 引用[2]给出了一个简单的例子: int a = 10; int *p = NULL; int **p1 = NULL; p = &a; p1 = &p; 那么,*p就是a的值(10),**p1就是先解引用p1得到p,再解引用p得到a(10)。 因此,二重指针解引用的意义可以总结为: 1. 一次解引用:得到它指向的那个一重指针。 2. 二次解引用:得到一重指针所指向的实际数据。 但是,我们还需要考虑解引用的使用场景,特别是当二重指针作为函数参数传递时(如引用[4]所述),通过解引用可以修改外部的一重指针。 另外,引用[3]提到了一个关于const和二重解引用的危险案例,这提醒我们在解引用时要注意类型匹配和const修饰符,以避免未定义行为。 下面,我将详细解释二重指针解引用的意义和作用。 ### 二重指针解引用的含义和作用 #### 1. 基本概念 - 二重指针(`int **pp`)存储的是一重指针(`int *p`)的地址。 - 一次解引用(`*pp`)得到的是该一重指针(`p`)的值,也就是一重指针所指向的变量的地址(如果一重指针指向一个整型变量`a`,那么`*pp`就是`&a`)。 - 二次解引用(`**pp`)得到的是这个一重指针指向的变量的值(即`a`的值)。 #### 2. 解引用的作用 **场景1:在函数中修改外部指针变量(引用[4]的应用)** 当我们需要在一个函数内部修改外部的一个指针变量时,必须传递这个指针变量的地址(即二重指针)。在函数内部,通过一次解引用,我们可以访问到外部指针变量,并修改它(比如让它指向新分配的内存)。 例如: ```c void allocate_memory(int **ptr, int size) { *ptr = malloc(size * sizeof(int)); // 一次解引用:修改外部指针指向新内存 if (*ptr != NULL) { // 初始化或其他操作 } } int main() { int *arr = NULL; allocate_memory(&arr, 10); // 传递指针arr的地址 if (arr) { arr[0] = 1; // 使用分配的内存 free(arr); } return 0; } ``` 这里,`*ptr`就是`arr`,所以`*ptr = malloc(...)`相当于`arr = malloc(...)`,从而修改了`main`函数中的`arr`指针。 **场景2:动态二维数组的访问** 当我们使用二重指针表示动态二维数组时,解引用用于访问数组元素。 ```c int **matrix = malloc(rows * sizeof(int*)); for (int i = 0; i < rows; i++) { matrix[i] = malloc(cols * sizeof(int)); } // 赋值 for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { matrix[i][j] = i * j; // 等价于:*(*(matrix + i) + j) = i * j; } } ``` 这里,`matrix[i]`(即`*(matrix+i)`)是一次解引用,得到第i行的首地址(一个一重指针),然后`matrix[i][j]`(即`*(*(matrix+i)+j)`)是二次解引用,得到第i行第j列的元素。 **场景3:多级间接访问** 在复杂的数据结构中(如链表中的链表,树结构等),我们可能需要通过多级指针来访问数据。解引用操作让我们能够逐级深入。 #### 3. 解引用与类型安全(引用[3]的警示) 二重指针解引用时,必须注意类型匹配和const修饰符,否则可能导致未定义行为。例如引用[3]中的例子: ```c const int n = 100; int *p; const int **pp; // 注意:这里pp是指向const int*的指针(即指向一个指向const int的指针) pp = &p; // 这是合法的,因为我们可以将非const指针的地址赋给指向const指针的指针(但存在风险) *pp = &n; // 这样,p就指向了n(因为*pp就是p) *p = 0; // 这是未定义行为,因为n是const,不能修改 ``` 这里,通过二重指针的解引用,我们绕过了类型系统,导致修改了const变量,这是危险的。 因此,在解引用二重指针时,要确保类型匹配,避免不安全的类型转换。 #### 4. 总结二重指针解引用的意义 - **一次解引用**:获取指向的一重指针,通常用于修改该一重指针(如改变它的指向)。 - **二次解引用**:获取一重指针指向的实际数据,用于读取或修改数据内容。 在实际编程中,二重指针解引用主要用于: - 函数中修改外部指针(一次解引用) - 访问动态分配的多维数组(一次解引用得到行指针,二次解引用得到元素) - 实现多级间接访问(如链表、树等复杂结构) 同时,我们应当警惕解引用过程中的类型安全问题,避免不合理的类型转换和const修饰符的破坏。 §§相关问题§§ 1. 二重指针解引用在数据结构中有哪些具体应用? 2. 如何安全地使用二重指针避免内存错误? 3. const修饰符与二重指针解引用之间有哪些需要注意的规则?</think>### 二重指针解引用的意义与作用详解 #### 1. **解引用的本质含义** 二重指针解引用是通过指针间接访问内存的过程: - **一级解引用 (`*p`)**: 获取指针指向的**地址值** ```c int a = 10; int *p = &a; // p存储a的地址 int **pp = &p; // pp存储p的地址 printf("%p", *pp); // 输出p的地址值(即&a) ``` 此时`*pp`等价于`p`,得到的是指向`a`的一级指针[^1][^2]。 - **二级解引用 (`**p`)**: 获取指针指向的**实际数据** ```c printf("%d", **pp); // 输出10(a的值) ``` 此时`**pp`等价于`*p`,最终访问到变量`a`的内存值[^2]。 #### 2. **解引用的核心作用** ##### (1) 修改外部指针(关键应用) 当需要修改函数外部的指针变量时,必须通过二重指针解引用实现: ```c // 错误:无法修改外部指针 void init_ptr(int *ptr) { ptr = malloc(sizeof(int)); // 仅修改局部副本 } // 正确:通过解引用修改原指针 void init_ptr(int **ptr) { *ptr = malloc(sizeof(int)); // 一级解引用修改目标指针 **ptr = 100; // 二级解引用修改目标数据 } int main() { int *p = NULL; init_ptr(&p); // 传入指针的地址 printf("%d", *p); // 输出100 } ``` 解引用`*ptr`直接操作`main()`中的`p`指针[^4]。 ##### (2) 动态多维数组访问 创建和访问动态二维数组: ```c int **matrix = malloc(3 * sizeof(int*)); // 分配行指针 for(int i=0; i<3; i++) { matrix[i] = malloc(4 * sizeof(int)); // 分配每行 // 等价于:*(matrix + i) = malloc(...) } // 访问元素 matrix[1][2] = 5; // 等价于:*(*(matrix + 1) + 2) = 5 ``` - `*(matrix + i)`:一级解引用获取行指针 - `*(*(matrix + i) + j)`:二级解引用获取元素值[^2] ##### (3) 多级间接访问 在树形结构中访问深层数据: ```c typedef struct Node { int data; struct Node **children; // 子节点数组 } Node; Node *root = create_tree(); int firstChildData = root->children[0]->data; // 等价于:*(*(root->children))->data ``` #### 3. **解引用的类型安全警示 解引用可能破坏类型系统(如引用[3]的案例): ```c const int n = 100; // 常量 int *p = NULL; // 普通指针 const int **pp = &p; // 危险的类型转换 *pp = &n; // 合法:const int* 指向常量 *p = 200; // 运行时错误!修改了常量内存 ``` 解引用操作`*pp`绕过类型检查,导致`p`指向常量区域,后续修改引发未定义行为[^3]。 #### 4. **解引用操作的意义总结** | 操作 | 作用 | 典型场景 | |-------------|-------------------------------|------------------------------| | **`*pp`** | 获取/修改目标指针的值 | 动态分配内存[^4]、指针重定向 | | **`**pp`** | 获取/修改目标指针指向的数据 | 访问多维数组、修改深层数据 | | **`&var`** | 获取指针的地址 | 传递二重指针参数 | #### 5. **解引用内存示意图** ``` pp p a +------+ +------+ +------+ | 0x100| --------> | 0x200| --------> | 10 | +------+ +------+ +------+ 地址0x100 地址0x200 地址0x200 操作: *pp = 0x200 (一级解引用) **pp = 10 (二级解引用) ``` > **核心意义**:二重指针解引用实现了**多级间接寻址**,通过类型系统保证内存访问安全,同时提供修改外部指针的能力[^1][^2][^4]。但需警惕类型转换风险(如引用[3]的const问题)[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值