一.F - Programming Contest (atcoder.jp)
(1)题目大意
给定你一个长度为n的序列,再给定你一个t,问你在这个n个数中得到的不超过t的和的最大值是多少?
(2)解题思路
很明显,直接状压枚举会超时,而直接dp会暴内存,因此我们考虑综合一下,对于前面一半我们采用状压存储所枚举的所有值,放入vector中,然后排序,对于后半部分的值,我们考虑直接状压枚举,然后跟vector中的值去比较能否二分到一个t-tmp的值。
(3)代码实现
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
const int N = 50;
int a[N];
void solve()
{
int n,t;
cin >> n >> t;
ll ans = 0,s = 0;
vector <int> v;
for(int i = 0;i < n;i++) cin >> a[i];
int h = n / 2,p = n - n / 2;
for(int i = 0;i < (1 << h);i ++) {
s = 0;
for(int j = 0;j < h;j++)
if(i >> j & 1)
s += a[j];
if(s <= t) v.push_back(s);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for(int i = 0;i < (1 << p);i++) {
s = 0;
for(int j = 0;j < p;j++)
if(i >> j & 1)
s += a[j + h];
if(s > t) continue;
auto it = lower_bound(v.begin(),v.end(),t - s);
if(it == v.end()) it --;
if(*(it) + s <= t) ans = max(ans,*(it) + s);
else if(it != v.begin()) it --,ans = max(ans,*(it) + s);
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;
T = 1;
while(T --) solve();
return 0;
}
(1)题目大意
给定你一串牌,每张卡都有一个数字标签ti,即点数。此外,每张卡都有一个值vi。鲍勃必须从这n张牌中选择一些牌,并将所选的牌分成两组,使两组中的牌的点数之和相等。然而,根据鲍勃的角色技能,在选择这两组牌之前,他最多可以选择k个不同的牌,并将它们的点数加倍。换句话说,他可以选择一个序列{a1,a2,⋯⋯,ar},(1≤a1<a2<⋯⋯<ar≤n,0≤r≤k),对于每个i(1≤i≤r),将tai变成2tai。之后他可以继续选择这两组。他们想知道他们最终得到的牌的价值的最大可能的总和。换句话说,在所有有效方案中确定最大的。
(2)解题思路
题目很长,但是可以想一想知道这是一个二维费用的背包问题,但要处理出点数平衡这个状态有点新奇。对于平衡这个状态,我们可以定义减去和加上这两个状态进行抵消,然后看题目发现ti最多13,n最大100,也就是说最多会有-2600,到2600这么多目标值,因此我们考虑把初始状态定义为2600。
然后分析状态转移方程
1.若不进行那此张牌
dp[i][j][k] = dp[i - 1][j][k];
2.若拿此张牌,给到Alice,但是不进行加倍操作
dp[i][j][k] = max(dp[i][j][k],dp[i - 1][j + w[i]][k] + v[i]);
3.若拿此张牌,给到Alice,但是进行加倍操作
dp[i][j][k] = max(dp[i][j][k],dp[i - 1][j + 2 * w[i]][k - 1] + v[i]);
4.若拿此张牌,给到Bob,但是不进行加倍操作
dp[i][j][k] = max(dp[i][j][k],dp[i - 1][j - w[i]][k] + v[i]);
5.若拿此张牌,给到Bob,但是进行加倍操作
dp[i][j][k] = max(dp[i][j][k],dp[i - 1][j - 2 * w[i]][k - 1] + v[i]);
答案就是max(dp[n][2600][i](i:1-k))
(3)代码实现
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
const int N = 105;
ll dp[N][5205][N];
int w[N],v[N];
int main()
{
memset(dp,-0x3f,sizeof(dp));
dp[0][2600][0] = 0;
ll ans = 0;
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++) scanf("%d%d",&v[i],&w[i]);
for(int i = 1;i <= n;i++) {
for(int j = 0;j <= 5200;j++) {
for(int k = 0;k <= m;k++) {
dp[i][j][k] = dp[i - 1][j][k];
if(j + w[i] <= 5200) dp[i][j][k] = max(dp[i][j][k],dp[i - 1][j + w[i]][k] + v[i]);
if(k && j + 2 * w[i] <= 5200) dp[i][j][k] = max(dp[i][j][k],dp[i - 1][j + 2 * w[i]][k - 1] + v[i]);
if(j - w[i] >= 0) dp[i][j][k] = max(dp[i][j][k],dp[i - 1][j - w[i]][k] + v[i]);
if(k && j - 2 * w[i] >= 0) dp[i][j][k] = max(dp[i][j][k],dp[i - 1][j - 2 * w[i]][k - 1] + v[i]);
}
}
}
for(int i = 0;i <= m;i++) ans = max(ans,dp[n][2600][i]);
printf("%lld",ans);
return 0;
}
(1)题目大意
给你k个材料,和一个能量x,每个裁量都有一个能量,高桥先生能任意拿k个材料,并且把他们合起来,最后必须要保证能到x,(注意k个材料合成后,每秒会有k的能量生长),请问最少的时间是多少?
(2)解题思路
对于x的范围有10^18,因此我们并不能从x开始下手,我们观察到N只有100,也就是说,我最多可以拿n个,实际上我们可以通过个数下手,定义dp[i][j]为有i个,%k值为j的最大值。
若dp[k][x % k]==-1说明这个状态没有,则不可能成为答案,否则(x - dp[k][x % k]) / k就是答案。
(3)代码实现
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
const int N = 110;
int a[N];
int main()
{
int n;
long long x;
cin >> n >> x;
for(int i = 1;i <= n;i++) cin >> a[i];
ll ans = 1e18;
for(int k = 1;k <= n;k++) {
vector <vector<ll>> dp(k + 1,vector<ll>(n,-1));
dp[0][0] = 0;
for(int i = 1;i <= n;i++) {
for(int j = k - 1;j >= 0;j--) {
for(int t = 0;t < k;t++) {
if(dp[j][t] == -1) continue;
ll nxt = dp[j][t] + a[i];
dp[j + 1][nxt % k] = max(dp[j + 1][nxt % k],nxt);
}
}
}
ll st = dp[k][x % k];
if(st == -1) continue;
ans = min(ans,(x - st) / k);
}
cout << ans << endl;
return 0;
}
(1)题目大意
(2)解题思路
首先我们需要发现一个贪心的性质,就是我只要取这个包,那我一定要尽可能拿完,也就是说我们可以把每个礼包看作一个整体,做一个01背包,但是这里又有一个limit,而且礼包大小不相同,因此我们需要考虑拿完一些整的背包后,剩下的该拿哪一个。如果直接枚举会是n*n*limit,我们想我们肯定要枚举最后一个这是跑不掉的,但是我们枚举的过程dp是不是可以优化一下呢?因为我们如果计算左边的背包,那么我们枚举右边区间的时候,实际上上只需要加上左边背包的价值,因此这题的思路就是贪心+分治优化01背包。
(3)代码实现
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
const int N = 2e3 + 10;
ll dp[25][N],c[N],s[N][N],ans;
int n,k;
void do_dp(int f,int l,int r)
{
for(int i = l;i <= r;i++)
for(int j = k;j >= c[i];j--)
dp[f][j] = max(dp[f][j],dp[f][j - c[i]] + s[i][c[i]]);
}
void init(int f)
{
for(int i = 0;i <= k;i++) dp[f][i] = dp[f - 1][i];
}
void dfs(int f,int l,int r)
{
if(l == r) {
ans = max(ans,dp[f][k]);
for(int i = 1;i <= c[l] && i <= k;i++) ans = max(ans,dp[f][k - i] + s[l][i]);
return ;
}
int mid = (l + r) >> 1;
init(f + 1);
do_dp(f + 1,l,mid);
dfs(f + 1,mid + 1,r);
init(f + 1);
do_dp(f + 1,mid + 1,r);
dfs(f + 1,l,mid);
}
int solve()
{
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= c[i];j++)
s[i][j] += s[i][j - 1];
}
dfs(1,1,n);
return ans;
}
class Solution {
public:
ll brilliantSurprise(vector<vector<int>>& present, int limit) {
k = limit;
n = ans = 0;
memset(dp,0,sizeof(dp));
for(auto x:present) {
c[++ n] = x.size();
for(int i = 0;i < c[n];i++) s[n][i + 1] = x[i];
}
return solve();
}
};
五.D-清楚姐姐学01背包(Hard Version)_2023牛客寒假算法基础集训营4 (nowcoder.com)
(1)题目大意
问你去掉某个背包的最大值为vmax,若加上当前这个背包的最大值为vmax2,若vmax<vmax2,则当前背包为必需背包,问你有n个背包,每个背包达到必需背包的最小价值要增加多少?
(2)解题思路
对于去掉某个背包这个限制条件我们可以,直接从前往后dp1一下计算前i个dp1的最大值,从后往前dp2一下最大值,然后对于去掉每个背包我们直接枚举重量为dp1[i-1][j]+dp2[i+1][j]的价值,然后用加上这个背包的最大价值减去去掉这个背包的最大价值+1,这个就是最小价值了。
(3)代码实现
#include "bits/stdc++.h"
#define rep(i,z,n) for(int i = z;i <= n; i++)
#define per(i,n,z) for(int i = n;i >= z; i--)
#define PII pair<int,int>
#define fi first
#define se second
#define vi vector<int>
#define vl vector<ll>
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) (x).begin(),(x).end()
using namespace std;
using ll = long long;
const int N = 5e3 + 10;
ll dp1[N][N],dp2[N][N];
int w[N],v[N],m,n;
ll calc(int idx)
{
ll lim=0,ans=2e18;
rep(j,0,m) lim=max(lim,dp1[idx-1][j]+dp2[idx+1][m-j]);
rep(j,0,m){
ll tv;
if(j>=w[idx]){
tv=dp1[idx-1][j-w[idx]]+v[idx]+dp2[idx+1][m-j];
ans=min(ans,max(lim-tv+1,0ll));
}
if(m-j>=w[idx]){
tv=dp1[idx-1][j]+dp2[idx+1][m-j-w[idx]]+v[idx];
ans=min(ans,max(lim-tv+1,0ll));
}
}
return ans;
}
void solve()
{
cin>>n>>m;
rep(i,1,n)cin>>w[i]>>v[i];
rep(i,1,n) rep(j,0,m){
dp1[i][j]=max(dp1[i-1][j],dp1[i][j]);
if(j>=w[i]) dp1[i][j]=max(dp1[i-1][j-w[i]]+v[i],dp1[i][j]);
}
per(i,n,1) rep(j,0,m){
dp2[i][j]=max(dp2[i+1][j],dp2[i][j]);
if(j>=w[i]) dp2[i][j]=max(dp2[i+1][j-w[i]]+v[i],dp2[i][j]);
}
rep(i,1,n) cout<<calc(i)<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T = 1;
// cin >> T;
while(T --) solve();
return 0;
}