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;
}