当前位置: 首页 > news >正文

背包dp与数位dp

背包dp

介绍

 动态规划实际上就是将复杂问题分解成若干个子问题,并通过子问题的解逐步发展成整体问题的解的算法思想。(我感觉这个解释就跟递归的思想一样)
 背包问题分为01背包(物体只能使用一次),完全背包(物体可以使用无数次),多重背包问题,混合背包问题、组合背包问题等等。下面将根据模板题来进行说明。

解法:
一、如果装不下当前物品,那么前 n 个物品的最佳组合和前 n - 1 个物品的最佳组合是一样的。
二、如果装得下当前物品。
假设 1: 装当前物品,在给当前物品预留了相应空间的情况下,前 n - 1 个物品的最佳组合加上当前物品的价值就是总价值。
假设 2: 不装当前物品,那么前 n 个物品的最佳组合和前 n - 1 个物品的最佳组合是一样的。
选取假设 1 和假设 2 中较大的价值,为当前最佳组合的价值。

就建一个如下图所示的表,按以上操作推导表中的所有数据。

模板

#include<bits/stdc++.h>
 using namespace std;
 const int MaxN=1010;
 int w[MaxN],v[MaxN];
 int f[MaxN][MaxN];//表示前i个物品,背包容量为j下的最优解
 int n,m;

 void my_solutin()
 {
	 for(int i=1;i<=n;i++)
	 {
		 for(int j=1;j<=m;j++)
		 {
			 if(j<v[i]) f[i][j]=f[i-1][j];
			 else f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
			 cout<<f[i][j]<<" ";
		 }
		 cout<<endl;
	 }
	 cout<<f[n][m];
 }
 int main()
 {
	 cin>>n>>m;
	 for(int i=1;i<=n;i++) cin>>v[i]>>w[i]
	 my_solutin();
	 return 0;
 }

用一维数组来完成:

我们在计算第i个物品时,只依靠与i-1这个物品的状态,所以不必追踪每一个物品的清晰状态,在这种条件下,他的时间复杂度就变成了O(n*m);
外层循环 (i): 遍历每一个物品。物品的索引从 1 到 n。
内层循环 (j): 遍历背包的容量,从 m 到 v[i]。之所以是从大到小遍历,而不是从小到大,是为了避免在同一个物品上多次计算。比如,如果我们从小到大更新 f[j],在更新 f[j] 时,如果使用了 f[j-v[i]],它可能会在计算过程中被覆盖,导致错误。
状态转移:
f[j] = max(f[j], f[j-v[i]] + w[i]);:
f[j]表示背包容量为j时的最大价值。
f[j-v[i]]表示背包容量为j-v[i]时的最大价值,即考虑放入第 i 个物品后,剩余的背包容量的最优解。
f[j-v[i]] + w[i]表示放入第 i 个物品后,背包容量为 j 时的最大价值。我们比较放入与不放入的情况,选择较大的值。

 #include<bits/stdc++.h>
 using namespace std;
 const int MaxN=1010;
 int w[MaxN],v[MaxN];
 int f[MaxN];// f[j]表示背包容量为j时的最大价值
 int n,m;
 
 void my_solutin()
 {
	 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] << endl;
 }
 int main()
 {
 	 cin>>n>>m;
	 for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
	 my_solutin();
	 return 0;
 }

背包dp例题

时间不超过t的情况随便采什么,就是背包问题

#include<bits/stdc++.h>
using namespace std;
const int MaxN = 1010;
int w[MaxN], v[MaxN];
int f[MaxN];// f[j]表示背包容量为j时的最大价值
int t, m;

void fun()
{
	for (int i = 1; i <= m; i++)
		for (int j = t; j >= v[i]; j--)
			f[j] = max(f[j], f[j - v[i]] + w[i]);
	cout << f[t] << endl;
}
int main()
{
	cin >> t >> m;
	for (int i = 1;i <= m;i++) cin >> v[i] >> w[i];
	fun();
	return 0;
}

背包问题一半版,只考虑容量,没有价值了

#include<bits/stdc++.h>
using namespace std;
const int MaxN=1010;
const int sss=20010;
int v[MaxN];
long long int f[sss];// f[j]表示背包容量为j时的最大价值
int mv,n;
void my_solutin()
{
	for(int i = 1; i <= n; i++) 
		for(int j = mv; j >= v[i]; j--) 
			f[j] = max(f[j], f[j-v[i]]+v[i]);
	cout << mv-f[mv] << endl;
}
signed main()
{
	cin>>mv>>n;
	for(int i=1;i<=n;i++) cin>>v[i];
	my_solutin();
	return 0;
}

比较有意思的一个题(没有标签我应该想不到可以用背包dp写),m当作体积,f[i][j]中的i可以看作是每一个正整数的序号,而每个序号所代表的正整数值便是他的体积量,这样一来就转化成了01背包问题,即选择体积和为m的最大数量;
 首先考虑它的状态表示,f[i][j]表示前i个数,且当前的总和恰好是j的方案。
 如果不选这个数,f[i][j]=f[i-1][j];
 如果选这个数,f[i][j]=max(f[i-1][j],f[i-1][j-a[i]);
当前状态是f[0][0],那么我们的目标状态是f[n][m];

 #include<bits/stdc++.h>
 using namespace std;
 const int MaxN=10010;
 int n,m;
 int a[MaxN];
 int f[MaxN];
 void solve()
 {
	 for(int i=1;i<=n;i++)
	 {
		 for(int j=m;j>=a[i];j--)
		 {
			 f[j]+=f[j-a[i]];
		 }
	 }
	 cout<<f[m]<<endl;
 }
 int main()
 {
	 f[0]=1;
	 cin>>n>>m;
	 for(int i=1;i<=n;i++) cin>>a[i];
	 solve();
	 return 0;
 }

数位dp

介绍

数位DP往往都是这样的题型,给定一个闭区间[l,r],让你求这个区间中满足某种条件的数的总数。数位DP就是换一种暴力枚举的方式,使得新的枚举方式符合DP的性质,然后预处理好即可。

所谓数位DP,就是在数的位上进行DP。这样的话,即便对 1e30 的大数,只需在31个数位上进行运算,显然会很高效。数位DP典型的数位分解思路如下图所示:

所谓数位DP,其实就是优化正常数数的过程。而正常数数的过程,其实就是一个dfs的过程。

数位DP问题可以采用前缀和的思想求解:即在求解过程中使用 [0, R] 的结果减去 [0, L-1] 的结果获得[L, R]的结果。这样做的好处在于只需要考虑区间的上边界即可。
数位DP=dfs+记忆化搜索

模板

#include <bits/stdc++.h>
using namespace std;
 
const int maxn=15;
 
int f[maxn][maxn]; //f[i][j]:一共有i位,且最高位是j的方案数
int L,R;
 
void init() {
    for(int i=0; i<=9; i++)
        f[1][i]=1;
    for(int i=2; i<maxn; i++)
        for(int j=0; j<=9; j++)
            for(int k=j; k<=9; k++)
                f[i][j]+=f[i-1][k];
}
 
int dfs(int n) {
    if(!n) return 1;
    vector<int> num;
 
    while(n) {
        num.push_back(n%10);
        n/=10;
    }
 
    int ans=0;
    int pre_num=0; //上一位数
    for(int i=num.size()-1; i>=0; i--) {
        int tmp=num[i];
        for(int j=pre_num; j<tmp; j++)
            ans+=f[i+1][j];
        if(tmp<pre_num)
            break;
        pre_num=tmp;
        if(!i)
            ans++;
    }
    return ans;
}
 
int main() {
    init();
    while(cin>>L>>R)
        cout<<dfs(R)-dfs(L-1)<<endl;
 
    return 0;
}
 

数位dp例题

数字游戏

题目描述
科协里最近很流行数字游戏。某人命名了一种不降数,这种数字必须满足从左到右个位数字呈现小于等于的关系,如123,446.现在大家决定玩一个游戏,指定一个整数闭区间[a,b],问这个区间内有多少个不降数。

输入
输入多组测试数据。每组只包含两个数字a和b(1<=a,b<=2^31)

输出
每行给出一个测试数据的答案,即[a,b]之间有多少不降数。

样例输入
1 9
1 19
样例输出
9
18

这题的约束条件是低位数永远大于高位数,所以只要控制当i<pre时continue。

#include<bits/stdc++.h>
using namespace std;
long long int n,m;
int a[20],dp[20][10];//a用来存放数字的每位数
int dfs(int pos,int pre,bool limit)//pre表示pos位数的下一位数的值,例如pos=3表示当前位为百位,则pre表示十位数的值
{
    if(pos==0)
        return 1;
    if(!limit&&dp[pos][pre]!=-1)
        return dp[pos][pre];
    int ans=0;
    int up=limit?a[pos]:9;
    for(int i=0;i<=up;i++)
    {
        if(i<pre)
            continue;
        ans+=dfs(pos-1,i,i==a[pos]&&limit);//从数字的高位向低位递归,新limit的取值根据原limit和i的取值决定,例如246,当pos表示十位数时,如果原limit=1(百位数=2)并且十位数等于4,那么下一次的搜索就会有限制(个位数上界限制为6)
    }
    if(!limit)
        dp[pos][pre]=ans;
    return ans;
}
 
int solve(int x)
{
    int pos=0;//pos用于记录当前位数
    while(x)
    {
        a[++pos]=x%10;//pos从1开始表示位数从个位到高位
        x/=10;//如246在数组中的存放方式是6 4 2
    }
    return dfs(pos,0,1);//从数字的最高位开始
}
 
int main()
{
    while(cin>>n>>m)
    {
        memset(dp,-1,sizeof(dp));
        cout<<solve(m)-solve(n-1)<<endl;//减的是n-1不是n,n自身要算进去
    }
    return 0;
}

算阶例题

按每头奶牛对最小阳光需求度的大小从大到小排序一遍,然后每次在防晒霜选择固定阳光度最大的防晒霜

#include <bits/stdc++.h>


using namespace std;
const int N = 3000;

int c, l, ans;

struct cow {
	int a, b;
} a[N];

struct spf {
	int a, b;
} b[N];

inline bool cmp(cow x, cow y) {return x.a > y.a;}
inline bool cmp_s(spf x, spf y) {return x.a > y.a;}

int main() {
//	freopen("in.txt", "r", stdin);
	
	scanf("%d %d", &c, &l);
	for(int i = 1; i <= c; ++i) scanf("%d %d", &a[i].a, &a[i].b);
	for(int i = 1; i <= l; ++i) scanf("%d %d", &b[i].a, &b[i].b);
	
	sort(a + 1, a + 1 + c, cmp);//按minspf递减的顺序排序 
	sort(b + 1, b + 1 + l, cmp_s);//按固定防晒度从大到小排序 
	
	for(int i = 1; i <= c; ++i)
		for(int j = 1; j <= l; ++j) {
			if(b[j].a >= a[i].a && b[j].a <= a[i].b && b[j].b) {
				ans++;
				b[j].b--;
				break;//找到了就直接找下一头奶牛可用的防晒霜 
			}
		}
		
	printf("%d\n", ans);
	return 0;
}

相关文章:

  • 智慧医疗心脏超声图像间隔壁分割数据集labelme格式3092张1类别
  • iframe 高さ 自動調整
  • 【网络安全 | 漏洞挖掘】我如何通过Cookie Manipulation发现主域上的关键PII?
  • Linux系统上同时打印到物理打印机并生成PDF副本方法研究
  • 【分布式理论14】分布式数据库存储:分表分库、主从复制与数据扩容策略
  • 汽车零部件开发应该具备哪些编程思维?
  • 内核崩溃分析
  • UMLS数据下载及访问
  • 使用 Python 将爬取的内容保存到 Excel 表格
  • DeepSeek的100个应用场景
  • 长文档处理痛点:GPT-4 Turbo引文提取优化策略与替代方案讨论
  • express + vue 部署宝塔
  • 从月牙定理看古希腊数学的奇妙突破
  • 【Python学习 / 6】面向对象编程(OOP)
  • C++中变量与容器的默认初始化:0的奥秘
  • URL解码(unquote)判断与操作教程
  • 1、Window Android 13模拟器 将编译的映像文件导入Android Studio
  • python如何从路径中获取文件名
  • Helm快速开始
  • Ubuntu 下 nginx-1.24.0 源码分析 - ngx_pnalloc函数
  • 让“五颜六色”面孔讲述上海故事,2025年上海城市推荐官开启选拔
  • 长沙潮宗街内“金丝楠木老屋文旅博物馆”起火:明火已扑灭,无伤亡
  • 综艺还有怎样的新可能?挖掘小众文化领域
  • 第四轮伊美核谈判将于11日在阿曼举行
  • 涨知识|没想到吧,体育老师强调的运动恢复方法是错的?
  • 欧盟公布关税反制清单,瞄准美国飞机、汽车等产品