背包杂题
背包 \(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;
}