YbtOJ 「动态规划」 第1章 背包问题

背包

A. 【例题1】采药问题

一维dp和二维dp均可

//一维 
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e3 + 5;
int t , m , ans;
struct yao
{
	int t , w;
}a[N];
int f[N][N];//f[i][j]表示当前进行到i草药,耗费j时间的的总价值; 
int main()
{
	scanf ( "%d%d" , &t , &m );
	for ( int i = 1 ; i <= m ; i ++ )
		scanf ( "%d%d" , &a[i].t , &a[i].w );
	for ( int i = 1 ; i <= m ; i ++ )
		for ( int j = t ; j >= a[i].t ; j -- )
			f[j] = max ( f[j-a[i].t] + a[i].w, f[j] );
	printf ( "%d" , f[t] );
	return 0;
}
//二维
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e3 + 5;
int t , m , ans;
struct yao
{
	int t , w;
}a[N];
int f[N][N];//f[i][j]表示当前进行到i草药,耗费j时间的的总价值; 
signed main()
{
	scanf ( "%lld%lld" , &t , &m );
	for ( int i = 1 ; i <= m ; i ++ )
		scanf ( "%lld%lld" , &a[i].t , &a[i].w );
	for ( int i = 1 ; i <= m ; i ++ )
	{
		for ( int j = 1 ; j <= t ; j ++ )
			f[i][j] = f[i-1][j]; 
		for ( int j = a[i].t ; j <= t ; j ++ )
			f[i][j] = max ( f[i-1][j] , f[i-1][j-a[i].t] + a[i].w );
	}
	printf ( "%lld" , f[m][t] );
	return 0;
}

B. 【例题2】货币系统

转化题意后 目标为要筛掉所有能被其他钱表示出来的钱 最后剩下来的就是必须保留的钱

对于每一个面值 向上枚举所有能用它表示出来的钱即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f;
int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , a[N] , f[N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	int T = read();
	while ( T -- )
	{
		memset ( f , 0 , sizeof f );
		n = read();
		for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
		sort ( a + 1 , a + n + 1 );
		int ans = 0;
		for ( int i = 1 ; i <= n ; i ++ )
		{
			if ( f[a[i]] ) continue;
			ans ++ , f[a[i]] = 1;
			for ( int k = a[i] ; k <= a[n] ; k ++ )
				if ( f[k-a[i]] ) f[k] = 1;
		}
		cout << ans << endl;
	}

	return 0;
}

C. 【例题3】宝物筛选

二进制拆分模板题 跑01背包即可

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

const int N = 5e4 + 5;
int n , W , ans , f[N] , tot;
struct node
{
	int val , wei;
}a[N];
signed main()
{
	scanf ( "%lld%lld" , &n , &W );//分别表示宝物种数和采集车的最大载重
	for ( int i = 1 , val , wei , m ; i <= n ; i ++ )
	{
		scanf ( "%lld%lld%lld" , &val , &wei , &m );
		for ( int j = 1 ; j <= m ; j <<= 1 )
		//把原来的物品拆成好几个能随意组合的物品(1,2,4,8...)并记录下对应的代价 
		{
			a[++tot] = { j * val , j * wei };
			m -= j;
		}
		if ( m ) a[++tot] = { m * val , m * wei };
	}
	for ( int i = 1 ; i <= tot ; i ++ )//多重背包拆分成01背包
		for ( int j = W ; j >= a[i].wei ; j -- )
			f[j] = max ( f[j] , f[j-a[i].wei] + a[i].val );
	printf ( "%lld" , f[W] );
}

D. 【例题4】硬币方案

二进制拆分后跑01背包 注意\(vis[0]=1\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long 
#define mid ((l+r)>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
const int N = 4e6 + 5;

int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , cnt , a[N] , c[N] , val[N] , num[N] , vis[N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	while ( cin >> n >> m && ( n || m ) )
	{
		cnt = 0;
		memset ( vis , 0 , sizeof vis );
		for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
		for ( int i = 1 ; i <= n ; i ++ ) c[i] = read();
		for ( int i = 1 ; i <= n ; i ++ )
		{
			for ( int j = 1 ; j <= c[i] ; j <<= 1 )
			{
				val[++cnt] = a[i] * j;
				c[i] -= j;
			}
			if ( c[i] ) val[++cnt] = a[i] * c[i];
		}
		vis[0] = 1;
		for ( int i = 1 ; i <= cnt ; i ++ )
			for ( int j = m ; j >= val[i] ; j -- )
				if ( !vis[j] && vis[j-val[i]] ) vis[j] = 1;
		int ans = 0;
		for ( int i = 1 ; i <= m ; i ++ ) 
			if ( vis[i] ) ans ++;
		cout << ans << endl;
	}
	return 0;
}

E. 【例题5】金明的预算方案

将每一个主件和它的下属节点读入

枚举每一个主件 枚举能加入的附件并更新权值

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , cnt[N] , wei[N][3] , val[N][3] , f[N]
;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	m = read() , n = read();
	for ( int i = 1 ; i <= n ; i ++ ) 
	{
		int v = read() , p = read() , q = read();
		if ( !q ) wei[i][0] = v , val[i][0] = v * p;
		else wei[q][++cnt[q]] = v , val[q][cnt[q]] = v * p;
	}
	for ( int i = 1 ; i <= n ; i ++ )
	{
		for ( int j = m ; j >= wei[i][0] ; j -- )
		{
			f[j] = max ( f[j] , f[j-wei[i][0]] + val[i][0] );
			if ( wei[i][0] + wei[i][1] <= j ) f[j] = max ( f[j] , f[j-wei[i][0]-wei[i][1]] + val[i][0] + val[i][1] );
			if ( wei[i][0] + wei[i][2] <= j ) f[j] = max ( f[j] , f[j-wei[i][0]-wei[i][2]] + val[i][0] + val[i][2] );
			if ( wei[i][0] + wei[i][1] + wei[i][2] <= j ) f[j] = max ( f[j] , f[j-wei[i][0]-wei[i][1]-wei[i][2]] + val[i][0] + val[i][1] + val[i][2] );
		}
		
	}
	cout << f[m] << endl;
	return 0;
}

F. 1.求好感度

二进制拆分即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N = 1e7 + 5;
		
int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , val[N] , wei[N] , cnt , f[N];

signed main ()
{
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ )
	{
		int num = read() , v = read() , w = read();
		for ( int j = 1 ; j <= num ; j <<= 1 ) val[++cnt] = v * j , wei[cnt] = w * j , num -= j;
		if ( num ) val[++cnt] = v * num , wei[cnt] = w * num;
	}
	for ( int i = 1 ; i <= cnt ; i ++ )
		for ( int j = m ; j >= wei[i] ; j -- )
			f[j] = max ( f[j] , f[j-wei[i]] + val[i] );
	cout << f[m] << endl;
	return 0;
}

G. 2.购买商品

发现A有最多10种 所以可以先暴搜出所有的A商品价格组合 那么余下的价值买B商品做完全背包

要注意当搜出来的价值相同的时候需要计入重量最小的结果

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N = 4e7 + 5;
const int inf = 0x3f3f3f3f;
		
int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , maxval , maxwei , f[N] , money;//总钱数 

struct node { int wei , val; } a[N] , b[N];

void dfs ( int pos , int v , int w )//物品 钱数 重量
{
	if ( w > money ) return ;
	if ( pos > n )	
	{
		if ( v > maxval || ( v == maxval && w < maxwei ) )
			maxval = v , maxwei = w;
		return;
	}
	dfs ( pos + 1 , v + a[pos].val , w + a[pos].wei );
	dfs ( pos + 1 , v , w );
}


signed main ()
{
	money = read();
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i].wei = read() , a[i].val = read();
	m = read();
	for ( int i = 1 ; i <= m ; i ++ ) b[i].wei = read() , b[i].val = read();
	dfs ( 1 , 0 , 0 );
	money -= maxwei;
	for ( int i = 1 ; i <= m ; i ++ )
		for ( int j = b[i].wei ; j <= money ; j ++ )
			f[j] = max ( f[j] , f[j-b[i].wei] + b[i].val );
	cout << f[money] << endl;
	return 0;
}

H. 3.魔法开锁

神题

\(dp[i][j]\)表示前\(i\)个环里选了$j $个点的方案数

预处理\(c\)数组组合数 方便后面求

那么$dp[i][j]+=dp[i-1][k]*c[cir[i]][j-k] $​(cir表示环的大小)注意转移边界\(0\le k < j\)

问题的答案就是\(dp[cnt][t]/c[n][t]\)

注意:\(f,c\)数组都要开成\(double\) 因为会爆\(longlong\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long 
const int N = 1e3 + 5;

int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , t , cnt , vis[N] , cir[N] , a[N];
double f[N][N] , c[N][N];//f[i][[j]表示前 i 个环里选了 j 个点的方案数 枚举k从0到j-1(因为

void init()
{
	c[0][0] = 1;
	for ( int i = 1 ; i <= 300 ; i ++ )
	{
		c[i][0] = 1;
		for ( int j = 1 ; j <= i ; j ++ ) 
			c[i][j] = c[i-1][j] + c[i-1][j-1];
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	int T = read();
	init();
	while ( T -- )
	{
		cnt = 0;
		memset ( f , 0 , sizeof f );
		memset ( vis , 0 , sizeof vis );
		memset ( cir , 0 , sizeof cir );
		n = read() , t = read();
		for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
		for ( int i = 1 ; i <= n ; i ++ )
			if ( !vis[i] )
			{
				int sz = 0 , now = i;
				while ( !vis[now] ) vis[now] = 1 , sz ++ , now = a[now];
				cir[++cnt] = sz;
			}
		f[0][0] = 1;
		for ( int i = 1 ; i <= cnt ; i ++ )
			for ( int j = 0 ; j <= t ; j ++ )
				for ( int k = 0 ; k < j ; k ++ )
					f[i][j] += f[i-1][k] * c[cir[i]][j-k];
		cout << 1.0 * f[cnt][t] / c[n][t] << endl;
	}
	return 0;
}

I. 4.购买礼物

考虑设置\(f[i][j][k][1/0]\)表示第\(i\)个物品 第一张信用卡用了\(j\)元钱 第二张用了\(k\)元钱 免费次数使用/没使用的最大价值

对于必选点 直接将这个点状态赋值为\(-inf\) 保证以后必须转移它 如果最后答案转移的权值小于等于0 那么直接输出-1

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long 
const int N = 4e5 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
		
int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , V1 , V2 , wei[N] , val[N] , need[N] , f[305][505][55][2];

signed main ()
{
	V1 = read() , V2 = read() , n = read();
	for ( int i = 1 ; i <= n ; i ++ ) wei[i] = read() , val[i] = read() , need[i] = read();
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = 0 ; j <= V1 ; j ++ )
			for ( int k = 0 ; k <= V2 ; k ++ )
			{
				if ( need[i] ) f[i][j][k][0] = f[i][j][k][1] = -inf;//强制保证选取
				else f[i][j][k][0] = f[i-1][j][k][0] , f[i][j][k][1] = f[i-1][j][k][1];
				
				f[i][j][k][1] = max ( f[i][j][k][1] , f[i-1][j][k][0] + val[i] );
				
				if ( wei[i] <= j )
				{
					f[i][j][k][0] = max ( f[i][j][k][0] , f[i-1][j-wei[i]][k][0] + val[i] );
					f[i][j][k][1] = max ( f[i][j][k][1] , f[i-1][j-wei[i]][k][1] + val[i] );
				}
				if ( wei[i] <= k ) 
				{
					f[i][j][k][0] = max ( f[i][j][k][0] , f[i-1][j][k-wei[i]][0] + val[i] );
					f[i][j][k][1] = max ( f[i][j][k][1] , f[i-1][j][k-wei[i]][1] + val[i] );
				}
			}
	int ans = max ( f[n][V1][V2][0] , f[n][V1][V2][1] );
	if ( ans > 0 ) cout << ans << endl;
	else cout << -1 << endl;
	return 0;
}

J. 5.课题选择

我们令\(dp[i][j]\)为表示前\(i\)个课题中完成\(j\)篇论文的最小耗时 枚举论文数量和课题数 再枚举课题\(i\)选择几个论文并计算权值

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long 
const int N = 1e3 + 5;
const int inf = 0x3f3f3f3f;
		
int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , f[N][N] , b[N] , a[N];
//令dp[i][j]为表示前i个课题中完成j篇论文的最小耗时

signed main ()
{
	n = read() , m = read();//n个论文 m个课题
	for ( int i = 1 ; i <= m ; i ++ ) a[i] = read() , b[i] = read();
	memset ( f , inf , sizeof f );
	for ( int i = 0 ; i <= m ; i ++ ) f[i][0] = 0;
	for ( int i = 1 ; i <= n ; i ++ )//枚举论文数量
		for ( int j = 1 ; j <= m ; j ++ ) //枚举课题数
			for ( int k = 0 ; k <= i ; k ++ )//枚举课题i选择多少个论文
				f[j][i] = min ( f[j][i] , f[j-1][i-k] + a[j] * (int)pow ( (double)k , (double)b[j] ) );
	cout << f[m][n] << endl;
	return 0;
}
posted @ 2023-06-27 22:38  Echo_Long  阅读(43)  评论(0编辑  收藏  举报