我是靠谱客的博主 微笑小刺猬,这篇文章主要介绍【01背包问题】【题目收集】1.dd爱科学2. 九小时九个人九扇门3.智乃买瓜4.爆炸的符卡洋洋洒洒5. 5倍经验日,现在分享给大家,希望可以做个参考。

1.dd爱科学

https://ac.nowcoder.com/acm/contest/11211/C

一串字符串,要求必须不递减,可以对字母进行修改,修改代价为原字母和修改后字母的差距,求最小修改代价和


状态表示:

f [ i ] [ j ] f[i][j] f[i][j]:前i个字母,第i个字母修改为j的最小代价

状态转移:
f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i − 1 ] [ k ] + a b s ( s [ i − 1 ] − ′ A ′ − j ) ) f[i][j] = min(f[i][j],f[i-1][k] + abs(s[i-1]-'A'-j)) f[i][j]=min(f[i][j],f[i1][k]+abs(s[i1]Aj))
因为可以从任意比j小的转移过来,所以对所有可以转移的情况取最小值

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5; 

int main()
{
	int n;
	cin>>n;
	vector<vector<int>>f(n+1,vector<int>(26,1e9));
	string s;
	cin>>s;
	
	f[0][0] = 0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<26;j++)
		{
//			f[i][j] = f[i-1][j] + abs(s[i-1]-'A'-j);
			for(int k=0;k<=j;k++)
				f[i][j] = min(f[i][j],f[i-1][k] + abs(s[i-1]-'A'-j));
		}
	}
	int res = 1e9;
	for(int i=0;i<26;i++)
		res = min(res,f[n][i]);
	cout<<res<<endl;
	return 0;
}

2. 九小时九个人九扇门

链接:
https://ac.nowcoder.com/acm/contest/23106/A

数字根:一个数字的数字跟为该数字各个位数之和,对于这个结果 再对其各个位数求和,直到求出来的结果为一个数字,即小于10,最终得出的结果为初始数字的数字根。
n个人,每个人都有一个数字,不同的人可以组合在一起。共标有1-9号数字的9扇门,只有数字跟和门上数字相等才能打开门,求开每种门的所有组合情况数。

数字根的几个性质

先对数字根分析:
对于两个数xy,可以发现 x+y的数字根 = = =·x的数字根+y的数字根
也就是说,数字根的计算顺序并不影响最终得出的数字根的结果

那么就可动态规划:
状态表示:
f [ i ] [ j ] f[i][j] f[i][j]:前i个人,数字根为j的组合情况
状态转移:
count()函数是计算数字根的函数
不选中第i个人时: f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i-1][j] f[i][j]=f[i1][j] (注意要先转移所有的不选中的,再进行下面的操作)
选中第i个人时: f [ i ] [ c o u n t ( j + a [ i ] ) ] + = f [ i − 1 ] [ j ] f[i][count(j+a[i])] += f[i-1][j] f[i][count(j+a[i])]+=f[i1][j]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+5,mod = 998244353;

ll a[N],f[N][10];
ll count(ll x)
{
	while(x>=10)
	{
		int cnt = 0;
		while(x)
		{
			cnt += x % 10;
			x /= 10;
		}
		x = cnt;
	}
	return x;
}

void solve()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) 
	{
		ll x;
		scanf("%lld",&x);
		a[i] = count(x);
	}
	//初始化
	for(int i=0;i<=n;i++) f[i][0] = 1;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=9;j++) f[i][j] = f[i-1][j];
		for(int j=0;j<=9;j++) f[i][count(j+a[i])] =(f[i][count(j+a[i])] + f[i-1][j]) % mod;
	}
	for(int i=1;i<=9;i++) printf("%lld ",f[n][i]);
}
int main()
{
	int t;
	t = 1;
	while(t--) solve();
 	return  0;
 } 

3.智乃买瓜

https://ac.nowcoder.com/acm/contest/23478/B

n个瓜,第i个瓜的重量为 w i w_i wi,对于每个西瓜,可以选择买一个,买半个,或者不买,求买瓜重量为k=1,2,3,...,M时,总方案数


状态表示:
f [ i ] [ j ] f[i][j] f[i][j]:前i个瓜,买了j重量的方案数
状态转移:
f [ i ] [ j ] = f [ i − 1 ] [ j − w i ] + f [ i − 1 ] [ j − w i 2 ] + f [ i − 1 ] [ j ] f[i][j]=f[i-1][j-w_{i}]+f[i-1][j-frac{w_i}{2}]+f[i-1][j] f[i][j]=f[i1][jwi]+f[i1][j2wi]+f[i1][j]

#include<bits/stdc++.h>
using namespace std;
const int N = 1e3+5;
typedef pair<int,int> pii;
typedef long long ll;
ll mod = 1e9+7;


int f[N][N];
int a[N];

void solve()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	
	for(int i=0;i<=n;i++) f[i][0] = 1;
	
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=m;j++)
		{
			f[i][j] = f[i-1][j];
			if(j>=a[i]) f[i][j] = (f[i][j] + f[i-1][j-a[i]]) % mod;
			if(j>=a[i]/2) f[i][j] =(f[i][j] + f[i-1][j-a[i]/2])%mod;
		}
	}
	
	for(int i=1;i<=m;i++) cout<<f[n][i]<<" ";
	
}

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

4.爆炸的符卡洋洋洒洒

链接:
https://ac.nowcoder.com/acm/problem/230607

在这里插入图片描述


转移时注意:
只能从j=0f[i-1][j]值不为0(说明f[i-1][j]之前被计算过)开始转移

但是设置初始值为负无穷之后,这种情况就不需要刻意考虑了
下面是两种不同的写法


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 15002,mod = 1000000007;

ll f[1005][1005];
ll a[1005],b[1005];

void solve()
{
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>a[i]>>b[i];
	
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<k;j++)
			f[i][j] = f[i-1][j];

		for(int j=0;j<k;j++)
		{
		//因为j+a[i]是新生成的,不能和上一个循环写在一起
			if(f[i-1][j] || j==0)
				f[i][(j+a[i])%k] = max(f[i][(j+a[i])%k],f[i-1][j] + b[i]);
		}
	}
	if(f[n][0]) cout<<f[n][0]<<endl;
	else cout<<-1<<endl;
	
}

int main()
{
	int t;
//	scanf("%d",%t);
	t = 1;
//	cin>>t;
	while(t--) solve();
	return 0;
 } 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int main()
{
	int n,k;
	cin>>n>>k;
	vector<int>a(n+1), b(n+1);
	for(int i=1;i<=n;i++) cin>>a[i]>>b[i];
	
	vector<vector<ll>>f(n+1,vector<ll>(k,-1e18));
	
	f[0][0] = 0;
	for(int i=1;i<=n;i++)
		for(int j=0;j<k;j++)
	 	{
	 		f[i][j] = f[i-1][j];
	 		f[i][j] = max(f[i][j],f[i-1][(j-a[i]%k+k)%k]+b[i]);
		}
	
	if(f[n][0] <= 0) cout<<-1<<endl;
	else cout<<f[n][0]<<endl;
	return 0;
 } 

5. 5倍经验日

https://www.luogu.com.cn/problem/P1802

n个好友,要第i个好友至少需要 s i s_i si瓶药,胜利会获得 w i w_i wi经验,失败获得 l i l_i li经验,求获得最大经验再乘5


我尝试了新的一种写法
状态表示:
f [ i ] [ j ] f[i][j] f[i][j] : 前i个好友,药水数为j,获得最大经验值
状态转移:
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] + l o s e [ i ] , f [ i − 1 ] [ j − s [ i ] ] + w i n [ i ] ) f[i][j] = max(f[i-1][j] + lose[i],f[i-1][j-s[i]] + win[i]) f[i][j]=max(f[i1][j]+lose[i],f[i1][js[i]]+win[i])

我声明了两个数组dpndp,利于节省空间
ndp[j]代表当前物品的状态,等价于f[i][j]
dp[j]代表前一个物品的状态,等价于f[i-1][j]


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

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int n, m;
	cin >> n >> m;
	
	vector<ll> dp(m + 1, 0), ndp(m + 1, 0);
	
	for(int i = 1; i <= n; i++ )
	{
		int lose, win, s;
		cin >> lose >> win >> s;
		for(int j = 0; j <= m; j++)
		{
			ndp[j] = dp[j] + lose;
			if(j >= s) ndp[j] = max(dp[j - s] + win, ndp[j]);
		}
		dp = ndp;
	}	
	cout << dp[m] * 5 << "n";
	return 0;
}

最后

以上就是微笑小刺猬最近收集整理的关于【01背包问题】【题目收集】1.dd爱科学2. 九小时九个人九扇门3.智乃买瓜4.爆炸的符卡洋洋洒洒5. 5倍经验日的全部内容,更多相关【01背包问题】【题目收集】1.dd爱科学2.内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(78)

评论列表共有 0 条评论

立即
投稿
返回
顶部