7-2 拼题A打卡奖励 (25 分)(背包)
题意:
求,从 n 个物品中选(每个物品有体积 vi, 价值 wi ),总体积不超过 m 的最大价值?
n ≤ 1 e 3 , m ≤ 7 e 5 , v i ≤ 600 , w i ≤ 30 n ≤1e3, m ≤7e5, vi ≤600, wi ≤30 n≤1e3,m≤7e5,vi≤600,wi≤30。
思路:
如果按照经典套路:求总体积不超过m的最大价值:
先枚举所有物品,再枚举所有体积,这样时间复杂度为O(n*m),超时。
所以,需要转换一下:因为最大总价值不超过 w i ∗ n = 3 e 4 wi*n=3e4 wi∗n=3e4,所以:
求总价值恰好为 m 时的,最小体积。
先枚举所有物品,再枚举所有价值,这样的复杂度为 n ∗ w i ∗ n = 3 e 7 n*wi*n=3e7 n∗wi∗n=3e7,可以。
最后遍历所有价值,看价值最大能够到达多少,使得最小体积不超过 m。
状态表示: f [ j ] f[j] f[j],总价值恰好为 j 的最小体积。
初始化:因为求最小值,所以一开始初始化为正无穷。
但是要注意,f[0] 要赋值为 0:总价值为0,最小体积为0。
状态转移:
前面枚举价值 j,从大到小循环,最小到 w[i]
f[j] = min(f[j], f[j-w[i]]+v[i])
Code:
const int N = 200010;
int T, n, m, a[N];
int v[N],w[N],f[N];
int main(){
cin>>n>>m;
int sum=0;
for(int i=1;i<=n;i++) cin>>v[i];
for(int i=1;i<=n;i++) cin>>w[i],sum+=w[i];
mem(f,0x3f);
f[0]=0;
for(int i=1;i<=n;i++)
{
for(int j=sum;j>=w[i];j--)
{
f[j]=min(f[j],f[j-w[i]]+v[i]);
}
}
while(f[sum]>m) sum--;
cout<<sum;
return 0;
}
这是求,总体积恰好为 j 时,总价值的最小值;
还可以求,总体积至少为 j 时,总价值的最小值:
和上面问题相同之处:状态表示:
f [ j ] f[j] f[j],总体积至少为 j 时,总价值的最小值。
但是不同之处:状态转移:
- 当 j ≥ v[i] 时,说明总体积至少为 j,且大于当前物品体积,当前物品可选可不选。
当前物品不选,状态更新:从前 i-1 个位置选,总体积至少为 j。
当前物品选,状态更新:从前 i-1 个位置选,总体积至少为 j-v[i],再加上当前物品的价值w[i]。 - 当 j < v[i] 时,说明总体积至少为 j,且小于当前物品体积。
和原来的完全背包不同的是,这里的j是最小的总体积,可以小于当前体积,也就是说当前的物品也是可以拿的!
当前物品不选,状态更新:从前 i-1 个位置选,总体积至少为 j。
当前物品选,状态更新为当前物品的价值!
这种状态下就不能从前 i-1 种物品中选,使得总体积至少为 j-v[i]。因为当前 j-v[i] 是小于0的。所以这个状态就直接更新成当前物品的价值。
Code:
mem(f,0x3f,sizeof f);
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--){
if(j>=v[i]) f[j]=min(f[j],f[j-v[i]]+w[i]);
else f[j]=min(f[j],w[i]);
}
}
例题:
P2918 [USACO08NOV]Buying Hay S
精卫填海
1355D. Game With Array(思维)
题意:
给出n,m。
要构造一个长度为n,总和为m的数列。
同时还要给出一个数k,满足没有连续的位置之和为 k 或者 m-k。
问,能否构造成功?
思路:
如果m ≥ 2*n的话,能够构造成功。
前n-1个位置设为2,最后一个位置补差。令k=1。
这样,k不能找到,m-k 也不能找到。
Code:
const int N = 200010, mod = 1e9+7;
int T, n, m, a[N];
int main(){
cin>>n>>m;
if(m>=n*2){
cout<<"YES\n";
for(int i=1;i<n;i++) cout<<2<<" ";
cout<<m-(n-1)*2<<endl;
cout<<1;
}
else{
cout<<"NO\n";
}
return 0;
}
1362C. Johnny and Another Rating Drop(规律,进制)
题意:
定义两个数的贡献为其二进制中,对应位不同的位置个数。
给一个数n,判断1~n中,所有相邻数的贡献之和位多少?
思路:
0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
发现规律:
第0位,贡献数为n;
第1位,贡献数为n/2;
第2位,贡献数为n/2/2;
第3位,贡献数为n/2/2/2...
Code:
const int N = 200010;
ll T, n, m, a[N];
int main(){
cin>>T;
while(T--)
{
cin>>n;
ll ans=0;
while(n)
{
ans+=n;
n/=2;
}
cout<<ans<<endl;
}
return 0;
}
挺难想到的。
对于这样的二进制找规律的题,应该想到从每一位下手,而不是看整个数。