背包杂题

背包 \(dp\) 杂题

P1450 [HAOI2008] 硬币购物

正解是容斥? 但是背包能过

\(f[i][j]\) 表示现在考虑前 \(i\) 个硬币 价值为 \(j\) 的方案数

我们可以用前缀和优化这个过程 记录 \(sum[k]\) 数组表示包括当前这个硬币 整体价值为 \(k\) 的方案数(都是从上一层状态转移过来的)

那么对于 \(f[i][j]\) 能转移到这个状态的数只有 \([k-c[j]*d[j],k]\) 中的合法状态(从上一层转移过来的+本层转移过来的)

做一次背包即可

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first endl
#define getchar() cin.get()
#define int long long
const int N = 1e5 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

void write ( int x )
{
	if ( x > 9 ) write ( x / 10 );
	cout.put ( x % 10 + '0' );
}

int n , m , c[5] , d[5] , sum[N] , f[5][N];
//f[i][j] 表示现在考虑前i个硬币 付款价值为m的方案数

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
    for ( int i = 1 ; i <= 4 ; i ++ ) c[i] = read();
	n = read();
    for ( int i = 1 ; i <= n ; i ++ )
    {
        for ( int j = 1 ; j <= 4 ; j ++ ) d[j] = read();
		m = read();
		memset ( f , 0 , sizeof f );
		f[0][0] = 1;
		for ( int j = 1 ; j <= 4 ; j ++ )
		{
			memset ( sum , 0 , sizeof sum );
			for ( int k = 0 ; k <= m ; k ++ )
			{
				if ( k - c[j] >= 0 ) sum[k] = sum[k-c[j]] + f[j-1][k];
				else sum[k] = f[j-1][k];
			}
			for ( int k = 0 ; k <= m ; k ++ )
			{
				f[j][k] = sum[k] - sum[max(0ll,k-c[j]*(d[j]+1))];
				if ( k - c[j] * ( d[j] + 1 ) < 0 ) f[j][k] += sum[0];
			}
		}
		cout << f[4][m] << endl;
    }
    return 0;
}

P3985 不开心的金明

先将所有价值都减去一个 \(minn\) 使得所有东西的价值是 \(1/2/3\) 然后我们将 \(sum\) 累加上所有的值作为整体容积

设置 \(f[j][k]\) 表示 \(j\) 容积 选取 \(k\) 个物品 的最大价值

因为需要做 \(01\) 背包 所以 \(j\)\(k\) 都要倒序枚举

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
#define int long long
const int N = 1e3 + 5;
const int inf = 2e9 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , W , w[N] , v[N] , minn = inf , sumv , f[N][N] , ans;//总价值为i 选了j个物品的重要度总和最大值

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , W = read();
	for ( int i = 1 ; i <= n ; i ++ )
	{
		v[i] = read() , w[i] = read();
		minn = min ( minn , v[i] );
		sumv += v[i];
	}
	-- minn;
	for ( int i = 1 ; i <= n ; i ++ ) v[i] -= minn;
	sumv -= minn * n;
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = sumv ; j >= v[i] ; j -- )
			for ( int k = n ; k ; k -- )
				if ( j + k * minn <= W ) f[j][k] = max ( f[j][k] , f[j-v[i]][k-1] + w[i] );
	// for ( int i = 1 ; i <= sumv ; i ++ , cout.put(endl) )
	// 	for ( int j = 1 ; j <= n ; j ++ )
	// 		cout << f[i][j] << ' ';
	for ( int i = 1 ; i <= sumv ; i ++ )
		for ( int j = 1 ; j <= n ; j ++ )
			ans = max ( ans , f[i][j] );
	cout << ans << endl;
	return 0;
}

P1833 樱花

我们对于完全和多重分两种情况讨论

  • 如果是完全背包 那么正向做一次更新
  • 如果是多重背包 那么二进制拆分后转移
#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 1000 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , f[N] , tt[N] , cc[N] , hh1 , mm1 , hh2 , mm2 , cnt;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	hh1 = read() , mm1 = read() , hh2 = read() , mm2 = read() , n = read();
	m = hh2 * 60 + mm2 - hh1 * 60 - mm1;
	for ( int i = 1 ; i <= n ; i ++ ) 
	{
		int t = read() , c = read() , p = read();
		if ( p == 0 )
		{
			for ( int j = t ; j <= m ; j ++ )
				f[j] = max ( f[j] , f[j-t] + c );
		}
		else 
		{
			cnt = 0;
			for ( int k = 1 ; k <= p ; k <<= 1 )
			{
				++ cnt;
				tt[cnt] = k * t;
				cc[cnt] = k * c;
				p -= k;
			}
			if ( p ) ++ cnt , tt[cnt] = p * t , cc[cnt] = p * c;
			for ( int k = 1 ; k <= cnt ; k ++ )
				for ( int j = m ; j >= tt[k] ; j -- )
					f[j] = max ( f[j] , f[j-tt[k]] + cc[k] );
		}
	}
	cout << f[m] << endl;
    return 0;
}

P5365 [SNOI2017] 英雄联盟

状态的设置应考虑完备()

如果 \(f[i][j]\) 表示到第 \(i\) 个物品 \(j\) 种策略的最小花费 递推设置非常麻烦

那么我们可以让 \(f[i][j]\) 表示到第 \(i\) 个物品 \(j\) 花费的最多展示策略

易得转移为 \(f[j] = max ( f[j] , f[j-p*c[i]] * p )\)

那么最后从小到大枚举输出答案即可

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
#define int long long
const int N = 1e5 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , f[N] , k[N] , c[N] , summ , ans;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) k[i] = read();
	for ( int i = 1 ; i <= n ; i ++ ) c[i] = read() , summ += k[i] * c[i];
	f[0] = 1;
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = summ ; j >= 0 ; j -- )
			for ( int p = 0 ; p <= k[i] && p * c[i] <= j ; p ++ )
				f[j] = max ( f[j] , f[j-p*c[i]] * p );
	for ( int i = 1 ; i <= summ ; i ++ ) if ( f[i] >= m ) { ans = i; break; }
	cout << ans << endl;
	return 0;
}

P5662 [CSP-J2019] 纪念品

很巧妙的背包 本质是一个完全背包 转化很妙

因为题中给了"当天买的可以当天卖出" 这一条件 那么我们可以将第 \(i\) 天买入 \(j\) 天卖出的一次交易抽象成 第 \(i\) 天买 第 \(i+1\) 天卖出 第 \(i+1\) 天买入 以此类推到 \(j\)

这样我们为每一个物品赋的价值就是 后一天的价格减去前一天的价格 做 \(T-1\) 次完全背包即可

注意每次的总钱数需要累加上上一天的收益

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 1e3 + 5;
const int inf = 2e9 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

void write ( int x )
{
	if ( x < 0 ) cout.put('-') , x = -x;
	if ( x > 9 ) write ( x / 10 );
	cout.put ( x % 10 + '0' );
}

int T , w[N][N] , n , m , f[N*10];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	T = read() , n = read() , m = read();
	for ( int i = 1 ; i <= T ; i ++ ) for ( int j = 1 ; j <= n ; j ++ ) w[i][j] = read();
	for ( int i = 1 ; i < T ; i ++ )
	{
		memset ( f , 0 , sizeof f );
		for ( int j = 1 ; j <= n ; j ++ )
			for ( int k = w[i][j] ; k <= m ; k ++ )
				f[k] = max ( f[k] , f[k-w[i][j]] + w[i+1][j] - w[i][j] );
		m += f[m];
	}
	cout << m << endl;
	return 0;
}

P5322 [BJOI2019] 排兵布阵

我们可以将对于每一个城池的决策当成一个物品

先将城池排序 从小到大枚举我们要占领多少次城池和其需要的代价

然后做一次多重背包 对于每一个城池枚举要使用哪一种决策即可

注意一个城池只能使用一个决策 不能直接将所有城池混为一谈

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
#define int long long
const int N = 3e3 + 5;
const int inf = 2e9 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int s , n , m , v[N][N] , w[N][N] , f[N*N] , cnt , a[N][N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	s = read() , n = read() , m = read();
	for ( int i = 1 ; i <= s ; i ++ )
		for ( int j = 1 ; j <= n ; j ++ )
			a[j][i] = read();
	for ( int i = 1 ; i <= n ; i ++ )
	{
		sort ( a[i] + 1 , a[i] + s + 1 );
		for ( int j = 1 ; j <= s ; j ++ ) v[i][j] = a[i][j] * 2 + 1 , w[i][j] = i * j;
	}
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = m ; j ; j -- )
			for ( int k = 1 ; k <= s ; k ++ ) 
				if ( j - v[i][k] >= 0 )
					f[j] = max ( f[j] , f[j-v[i][k]] + w[i][k] );
	cout << f[m];
	return 0;
}

P2224 [HNOI2001] 产品加工

很奇怪的背包 \(dp\)

设置状态为 \(f[i][j]\) 表示到第 \(i\) 个物品 \(A\) 机器耗时为 \(j\)\(B\) 机器的耗时

显然有三个转移:

\(f[cur][j] = min ( f[cur][j] , f[pre][j-x] )\) (第一个机器做)
\(f[cur][j] = min ( f[cur][j] , f[pre][j] + y )\) (第二个机器做)
\(f[cur][j] = min ( f[cur][j] , f[pre][j-z] + z )\) (两个机器一起做)

注意特判下标小于 \(0\) 的情况

发现空间不够 可以滚动数组优化 于是做完了

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 3e4 + 5;
const int inf = 2e9 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , f[2][N] , sum , minn = inf;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ )
	{
		int cur = ( i & 1 ) , pre = cur ^ 1;
		memset ( f[cur] , 0x3f , sizeof f[cur] );
		int x = read() , y = read() , z = read();
		sum += max ( x , max ( y , z ) );
		for ( int j = 0 ; j <= sum ; j ++ )//枚举A机器可能做的最大值
		{
			if ( x && j - x >= 0 ) f[cur][j] = min ( f[cur][j] , f[pre][j-x] );//由第一个机器做
			if ( y ) f[cur][j] = min ( f[cur][j] , f[pre][j] + y );//由第二个机器做
			if ( z && j - z >= 0 ) f[cur][j] = min ( f[cur][j] , f[pre][j-z] + z );//两个机器一起做
		}
	}
	for ( int i = 0 ; i <= sum ; i ++ ) minn = min ( minn , max ( i , f[n&1][i] ) );//分配给a的时间和b的时间取max即可
	cout << minn << endl;
	return 0;
}

P2732 [USACO3.3] 商店购物 Shopping Offers

好题 考虑将每一条限制当成一个物品 标记它所需的物品个数和优惠价格(我们将原价也当成是一个物品 只不过标记的时候只标记对应编号 物品个数赋值为 \(1\) )

然后做一次五维完全背包即可(优惠劵可以选取多次)

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 20 + 5;
const int M = 200 + 5;
const int inf = 2e9 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int s , t , n , a[M][M] , val[M] , k[N] , c[N] , f[N][N][N][N][N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	s = read();
	for ( int i = 1 ; i <= s ; i ++ )
	{
		n = read();
		for ( int j = 1 , k ; j <= n ; j ++ ) k = read() , a[i][k] = read();
		val[i] = read();
	}
	t = read();
	memset ( f , 0x3f , sizeof f );
	f[0][0][0][0][0] = 0;
	for ( int i = 1 ; i <= t ; i ++ ) c[i] = read() , k[i] = read() , ++ s , val[s] = read() , a[s][c[i]] = 1;
	for ( int i = 1 ; i <= s ; i ++ )
	{
		int cnt1 = a[i][c[1]] , cnt2 = a[i][c[2]] , cnt3 = a[i][c[3]] , cnt4 = a[i][c[4]] , cnt5 = a[i][c[5]];
		for ( int a1 = cnt1 ; a1 <= k[1] ; a1 ++ )
			for ( int a2 = cnt2 ; a2 <= k[2] ; a2 ++ )
				for ( int a3 = cnt3 ; a3 <= k[3] ; a3 ++ )
					for ( int a4 = cnt4 ; a4 <= k[4] ; a4 ++ )
						for ( int a5 = cnt5 ; a5 <= k[5] ; a5 ++ )
							f[a1][a2][a3][a4][a5] = min ( f[a1][a2][a3][a4][a5] , f[a1-cnt1][a2-cnt2][a3-cnt3][a4-cnt4][a5-cnt5] + val[i] );
	}
	cout << f[k[1]][k[2]][k[3]][k[4]][k[5]] << endl;
	return 0;
}

P2214 [USACO14MAR] Mooo Moo S

我们可以预处理每一种价值最少需要的奶牛数量(背包)

然后对于每一个询问 按照题意计算该点本身牛的音量并统计即可

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

int n , b , f[N] , v[N] , ans;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , b = read();
	memset ( f , 0x3f , sizeof f );
	f[0] = 0;
	for ( int i = 1 ; i <= b ; i ++ )
	{
		v[i] = read();
		for ( int j = v[i] ; j <= maxn ; j ++ )
			f[j] = min ( f[j] , f[j-v[i]] + 1 );
	}
	for ( int i = 1 , now = 0 ; i <= n ; i ++ )
	{
		int x = read() , temp = x;
		x -= now;
		now = temp;
		now -= now ? 1 : 0;
		if ( x < 0 || f[x] == inf ) return cout << -1 << endl , 0;
		ans += f[x];
	}
	cout << ans << endl;
	return 0;
}

P2854 [USACO06DEC] Cow Roller Coaster S

我们设 \(f[i][j]\) 表示 \(0-i\) 之间所有位置都覆盖 代价为 \(j\) 的最大欢乐度

初始值全为 \(-1\) 表示不合法 \(f[0][0]=0\)

显然对于每一条线段按照起始点排序 对于每一条线段 枚举代价做一次背包即可

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

int L , n , B , f[N][N] , v[N] , ans = -1; //前i个位置 代价为j的最大欢乐度

struct node { int x , l , w , v; } t[10*N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	L = read() , n = read() , B = read();
	memset ( f , -1 , sizeof f );
	f[0][0] = 0;
	for ( int i = 1 ; i <= n ; i ++ ) t[i].x = read() , t[i].l = read() , t[i].w = read() , t[i].v = read();
	sort ( t + 1 , t + n + 1 , []( const node &a , const node &b ) { return a.x < b.x; } );
	for ( int i = 1 ; i <= n ; i ++ ) 
		for ( int j = t[i].v ; j <= B ; j ++ ) 
			if ( f[t[i].x][j-t[i].v] != -1 ) f[t[i].x+t[i].l][j] = max ( f[t[i].x+t[i].l][j] , f[t[i].x][j-t[i].v] + t[i].w );
	for ( int i = 0 ; i <= B ; i ++ ) ans = max ( ans , f[L][i] );
	cout << ans << endl;
	return 0;
}

P2967 [USACO09DEC] Video Game Troubles G

可以记录一个 \(g\) 数组表示累加上上一次的贡献之后 单独对这一次做 \(01\) 背包的价值 然后再对 \(f\) 数组取 \(max\) (\(f\) 数组表示的是 \(1-i\) 的累加价值)

第一版代码:

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 1e2 + 5;
const int M = 1e5 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , V , v[N] , cnt[N] , vv[N][N] , ww[N][N] , ans , f[N][M] , g[N][M];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , V = read();
	for ( int i = 1 ; i <= n ; i ++ )
	{
		v[i] = read() , cnt[i] = read();
		for ( int j = 1 ; j <= cnt[i] ; j ++ )
			vv[i][j] = read() , ww[i][j] = read();
	}
	for ( int i = 1 ; i <= n ; i ++ )
	{
		for ( int j = 0 ; j <= V ; j ++ ) g[i][j] = f[i][j] = f[i-1][j];
		for ( int j = 1 ; j <= cnt[i] ; j ++ )
			for ( int k = V ; k >= vv[i][j] ; k -- )
				g[i][k] = max ( g[i][k] , g[i][k-vv[i][j]] + ww[i][j] );
		for ( int k = V ; k >= v[i] ; k -- )
			f[i][k] = max ( f[i][k] , g[i][k-v[i]] );
	}
	for ( int i = 0 ; i <= V ; i ++ ) ans = max ( ans , f[n][i] ); 
	cout << ans << endl;
	return 0;
}

更快的代码:

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

int n , V , f[N] , g[N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , V = read();
	for ( int i = 1 ; i <= n ; i ++ )
	{
		int v = read() , cnt = read();
		for ( int j = v ; j <= V ; j ++ ) g[j] = f[j-v];
		for ( int j = 1 ; j <= cnt ; j ++ )
		{
			int vv = read() , ww = read();
			for ( int k = V ; k >= v + vv ; k -- ) g[k] = max ( g[k] , g[k-vv] + ww );
		}
		for ( int j = v ; j <= V ; j ++ ) f[j] = max ( g[j] , f[j] );
	}
	cout << f[V] << endl;
	return 0;
}

P2938 [USACO09FEB] Stock Market G

\(P5662\) 是一样的题目()

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

int n , m , s , f[500000+5] , a[N][N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	s = read() , n = read() , m = read();
	for ( int i = 1 ; i <= s ; i ++ )
		for ( int j = 1 ; j <= n ; j ++ )
			a[i][j] = read();
	for ( int i = 1 ; i < n ; i ++ )
	{
		memset ( f , 0 , sizeof f );
		for ( int j = 1 ; j <= s ; j ++ )
			for ( int k = a[j][i] ; k <= m ; k ++ )
				f[k] = max ( f[k] , f[k-a[j][i]] + a[j][i+1] - a[j][i] );
		m += f[m];
	}
	cout << m << endl;
	return 0;
}

P1282 多米诺骨牌

设置 \(f[i][j]\) 表示到了第 \(i\) 个骨牌 上下点数之差为 \(j\) 的最小旋转次数

这个点数之差可能为负值 所以我们加上一个大数 \(maxn\) 即可

因为 \(delta\) 的正负并非固定 所以不适合滚动数组()

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int inf = 0x3f3f3f3f;
const int N = 1e4 + 5;
const int maxn = 5e3;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , s , f[1005][N] , a[N] , b[N] , delta[N];//f[i]表示让上下点数之差达到i的最小旋转次数

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , b[i] = read() , delta[i] = a[i] - b[i];
	memset ( f , inf , sizeof f );
	f[0][maxn] = 0;
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = -maxn ; j <= maxn ; j ++ )
			f[i][j+maxn] = min ( f[i][j+maxn] , min ( f[i-1][j+maxn+delta[i]] + 1 , f[i-1][j+maxn-delta[i]] ) );
	for ( int i = 0 ; i <= maxn ; i ++ )
		if ( f[n][i+maxn] != inf || f[n][-i+maxn] != inf )
			return cout << min ( f[n][i+maxn] , f[n][-i+maxn] ) , 0;
	return 0;
}

P2979 [USACO10JAN] Cheese Towers S

肥肠奇特的背包 \(dp\) 如果不考虑大奶酪的话就是一个完全背包

当我们考虑大奶酪的时候 选择加一个状态 设置 \(f[i][0]\) 表示高度为 \(i\) 上面没有大奶酪 \(f[i][1]\) 表示有大奶酪的最大价值

注意 在转移 \(1\) 状态的时候必须保证以前的这个 \(1\) 状态被转移过了 否则只能从 \(0\) 状态转移到 \(1\)

这里如果将 \(i\)\(j\) 枚举顺序调换位置会 \(WA\) 一个点

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int inf = 0x3f3f3f3f;
const int N = 1e4 + 5;
const int maxn = 5e3;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , k , v[N] , w[N] , f[N][2] , maxx;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read() , k = read();
	for ( int i = 1 ; i <= n ; i ++ ) w[i] = read() , v[i] = read();
	for ( int i = 0 ; i <= m ; i ++ )
		for ( int j = 1 ; j <= n ; j ++ )
		{
			if ( i >= v[j] && v[j] < k ) f[i][0] = max ( f[i][0] , f[i-v[j]][0] + w[j] );
			if ( i >= v[j] && v[j] >= k ) f[i][1] = max ( f[i][1] , f[i-v[j]][0] + w[j] );
			if ( i >= v[j] / 5 * 4 && f[i-(v[j]/5*4)][1] ) f[i][1] = max ( f[i][1] , f[i-v[j]/5*4][1] + w[j] );
		}
	for ( int i = 0 ; i <= m ; i ++ ) maxx = max ( f[i][0] , f[i][1] );
	cout << maxx << endl;
	return 0;
}

P1417 烹调方案

我们可以对于相邻两个点 \(i,j\) 考虑哪一个更优 假设先 \(i\)\(j\) 比先 \(j\)\(i\) 更优 那么设当前时间为 \(t\) 有:

\(a_i-t*b_i+a_j-(t+c_i)*b_j>a_j-t*b_j+a_i-(t+c_j)*b_i\)

即:当 \(c_j*b_i>c_i*b_j\)\(i\) 在前更优

那么我们对于所有点进行这样的排序 然后 \(01\) 背包即可达到最优

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
#define int long long
const int N = 50 + 5;

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

int T , n , f[100000+5] , maxx;

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

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	T = read() , n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i].a = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i].b = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i].c = read();
	sort ( a + 1 , a + n + 1 , [](const node &a , const node &b) { return a.b * b.c > a.c * b.b; } );
	for ( int i = 1 ; i <= n ; i ++ ) 
		for ( int j = T ; j >= a[i].c ; j -- )
			f[j] = max ( f[j] , f[j-a[i].c] + a[i].a - j * a[i].b );
	for ( int i = 1 ; i <= T ; i ++ ) maxx = max ( maxx , f[i] );
	cout << maxx << endl;
	return 0;
}

P2170 选学霸

感觉背包确实很多变 这又跟并查集综合起来了

相当于是用并查集将所有实力相同的缩成一个物品再进行背包 最后从小到大枚举人数 输出最接近的

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
#define int long long
const int N = 2e4 + 5;
const int inf = 0x3f3f3f3f;

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

int n , m , k , fa[N] , v[N] , cnt , sz[N] , minn = inf , f[N] , ans;

int find ( int x ) { return fa[x] == x ? x : fa[x] = find(fa[x]); }

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read() , k = read();
	for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i , sz[i] = 1;
	for ( int i = 1 ; i <= k ; i ++ )
	{
		int fu = find(read()) , fv = find(read());
		if ( fu != fv ) fa[fu] = fv , sz[fv] += sz[fu];
	}
	for ( int i = 1 ; i <= n ; i ++ ) if ( fa[i] == i ) v[++cnt] = sz[i];
	for ( int i = 1 ; i <= cnt ; i ++ )
		for ( int j = n ; j >= v[i] ; j -- )
			f[j] = max ( f[j] , f[j-v[i]] + v[i] );
	for ( int i = 0 ; i <= n ; i ++ ) 
		if ( abs ( f[i] - m ) < minn ) minn = abs ( f[i] - m ) , ans = i;
	cout << ans << endl;
	return 0;
}

P1855 榨取kkksc03

相当于代价是二维的 那么我们也可以设置二维数组来推

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int N = 1e3 + 5;
int m , n , T , f[N][N] , v[N] , t[N];

signed main ()
{
	scanf ( "%lld%lld%lld" , &n , &m , &T );
	for ( int i = 1 ; i <= n ; i ++ )
		scanf ( "%lld%lld" , &v[i] , &t[i] ); 
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = m ; j >= v[i] ; j -- )
			for ( int k = T ; k >= t[i] ; k -- )
				f[j][k] = max ( f[j][k] , f[j-v[i]][k-t[i]] + 1 );
	printf ( "%lld" , f[m][T] );
	return 0;
} 

P2946 [USACO09MAR] Cow Frisbee Team S

十分基础的转移 但是第一次还是不太能想到用余数做数组第二维的 \(trick\)

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 2e3 + 5;
const int mod = 1e8;
const int inf = 0x3f3f3f3f;

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

int n , F , f[N][N] , a[N];//f[i][j]表示到第i头奶牛 总能力和modf=j的方案数

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , F = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() % F;
	for ( int i = 1 ; i <= n ; i ++ ) f[i][a[i]] = 1;
	for ( int i = 1 ; i <= n ; i ++ ) 
		for ( int j = 0 ; j < F ; j ++ )
			( f[i][j] += ( f[i-1][j] + f[i-1][(j-a[i]+F)%F] ) % mod ) %= mod;
	cout << f[n][0] << endl;
	return 0;
}

P1156 垃圾陷阱

去年贺的太离谱了 马蜂一看就不是自己的()

看到一个物品可以堆放也可以吃 想到背包

设置 \(f[i][j]\) 表示前 \(i\) 个物品 高度为 \(j\) 的时候的最大生命

显然可以滚动数组

那么我们记录 \(f[i]\) 表示高度为 \(j\) 的时候的最大生命

对于每一个东西 我们从后往前枚举高度 如果这个高度下可以存活 那么考虑这个垃圾吃/不吃的两种情况 即为:

\(f[j+a[i].h] = max ( f[j+a[i].h] , f[j] )\) 这种情况是不吃垃圾 将当前的生命值直接加到高垃圾上去

\(f[j] += a[i].f\) 这种情况下是吃垃圾 更新必须在不吃垃圾之后 因为我们要先用以前的不吃垃圾的生命值 来更新不吃垃圾的状态

\(code\):

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 2e3 + 5;
const int mod = 1e8;
const int inf = 0x3f3f3f3f;

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

int n , m , f[N];//f[i][j]表示到第i个垃圾 总能力和modf=j的方案数

struct node { int t , h , f; } a[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 ++ ) a[i].t = read() , a[i].f = read() , a[i].h = read();
	sort ( a + 1 , a + n + 1 , [](const node &a , const node &b) { return a.t < b.t; } );
	f[0] = 10;
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = m ; j >= 0 ; j -- )
			if ( f[j] >= a[i].t ) //能活着
			{
				if ( j + a[i].h >= m ) return cout << a[i].t << endl , 0;
				f[j+a[i].h] = max ( f[j+a[i].h] , f[j] );//不吃垃圾 直接堆起来 这条更新必须在吃垃圾之前
				f[j] += a[i].f;//吃垃圾
			}
	cout << f[0] << endl;
	return 0;
}

P4823 [TJOI2013] 拯救小矮人

考虑贪心 手长+臂长的一定要做垫脚石 所以我们按照顺序先排序 但是腿长手短的可以选择奉献自己 所以我们先排序再进行 \(dp\)

\(f[i][j]\) 表示考虑到前 \(i\) 个小矮人 一共走了 \(j\) 个能达到的最大高度

初始值 \(f[0]=\sum_{i=1}^na[i].a\)

当这个人能出去的时候 选择出去/不出去 即为 \(f[j]=max(f[j],f[j-1]-a[i].a)\)

统计答案即从大到小枚举走了多少人 找到第一个有值的输出即可

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 2e3 + 5;
const int mod = 1e8;
const int inf = 0x3f3f3f3f;

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

int n , m , f[N];//f[i]表示走了j个人时 人梯剩下的最大高度

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

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	memset ( f , -inf , sizeof f ) , f[0] = 0;
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i].a = read() , a[i].b = read() , f[0] += a[i].a;//"剩下的高度"指的是当前所有在场上的小矮人的高度
	m = read();
	sort ( a + 1 , a + n + 1 , [](const node &a , const node &b) { return a.a + a.b < b.a + b.b; } );
	for ( int i = 1 ; i <= n ; i ++ ) 
		for ( int j = i ; j ; j -- )
			if ( f[j-1] + a[i].b >= m ) f[j] = max ( f[j] , f[j-1] - a[i].a );
	for ( int i = n ; i >= 0 ; i -- ) if ( f[i] >= 0 ) return cout << i << endl , 0;
	return 0;
}
posted @ 2023-09-27 07:23  Echo_Long  阅读(5)  评论(0编辑  收藏  举报