[核桃] - P10769 冒险
[核桃] - P10769 冒险
大意
\(n\) 种物品,每种物品有重量 \(w_j\) 和价值 \(v_j\),一个模数 \(P\),背包容量无限(每种物品可以拿任意多件),总重量 \(W\) = 所选物品的重量之和,转轮位置 \(= W \mod P\),代价 = 所选物品的价值之和。任务:对于每个位置 \(i = 0, 1, ..., P-1\),计算:能达到位置 \(i\) 的最小代价如果无法达到位置 \(i\),输出 \(-1\)
思路
最开始看到这个题是想到了完全背包,就是对于每个 \(W \% P\) 去凑数,然后我们只需要去凑 \(W\),那么我们应该怎么凑呢?首先要考虑 dp,但是我们发现如果 dp 的话,很难去定义状态,因为这是个带取模问题的题目,那么我们就可以去想到凑硬币的那种同余最短路(类似)。
那么这道题,我们只需要对于每个余数为 \(i\) 的,将其最小价值 \(v\) 存储进去,这样的话我们在转移的时候选择每个余数的最小价值,好,那么具体需要注意的是,对于余数为 \(0\) 的,我们将其赋值为 \(\text{INF}\), 因为你在转移的过程中用余数是 \(0\) 的很扯淡,因为加上 \(0\) 并不会有任何贡献,然后边最多就是 \(P\) 条,我们在转移的过程中实际上是类似最短路的写法,加上最优化剪枝,这个题就没了。
代码
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
#define vc vector
#define eb emplace_back
#define ll long long
#define pr pair<ll, int>
const int MAXN = 2005;
const long long INF = 1e18;
int n, p;
ll c[MAXN], dp[MAXN];
vc<pr> f;
priority_queue<pr, vc<pr>, greater<pr> > q;
int main(){
freopen("venture.in", "r", stdin);
freopen("venture.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> p;
for(int i = 0;i < p;i ++){
c[i] = INF;
if(i == 0) dp[i] = 0;
else dp[i] = INF;
}
for(int i = 1;i <= n;i ++){
ll w, v; cin >> w >> v;
int r = w % p;
if(r != 0 && v < c[r]){
c[r] = v;
}
}
// for(int i = 1;i <= n;i ++){
// cout << c[i] << '\n';
// }
for(int i = 1;i < p;i ++){
if(c[i] != INF){
f.eb(i, c[i]);
}
}
q.push({0, 0});
while(!q.empty()){
pr now = q.top(); q.pop();
ll d = now.first; int x = now.second;
if(d > dp[x]) continue;
for(pr u : f){
int r = u.first; ll v = u.second;
int y = (x + r) % p;
// cout << r << ' ' << w << '\n';
if(dp[y] > d + v){
dp[y] = d + v;
q.push({dp[y], y});
}
}
}
for(int i = 0;i < p;i ++){
if(dp[i] == INF) cout << -1 << ' ';
else cout << dp[i] << ' ';
}
return 0;
}
本文来自一名高中生,作者:To_Carpe_Diem

浙公网安备 33010602011771号