[题解] 背包(同余)
[题解] 背包(同余)
题意
给你 \(n\) 个物品,个数无限,要求用最少的物品来塞满一个巨大的背包 \(M\),\(M\in[10^{10},10^{18}]\)。
解题报告
不妨观察一组样例:
10000000001 3
23 51 100
显然结果是:\(100000004\)
我相信你肯定是首先根据 \(100\) 这个物品来计算结果的,这提示我们一种算法:
- 凑出尽量大的,拼凑出剩下的。
所以类似于同余最短路吧,设 \(dis[u]\) 为装满 \(M\ mod\ P=u\) 的最少物品数量,其中把最大的物品的体积当做模数 \(P\)。
最后答案为:\(M/P+dis[u]\)。
实现
考虑怎么跑这个最短路。
无非是向外通过 \(v=u+a[i]\) 进行扩展,对于边权分一下两种情况:
-
\(v< P\),边权为 \(1\)。
-
\(v\geq P\),仔细分析发现,这个算法存在一种反悔操作:
即:如果装过了 \(P\),要退掉一个物品 \(P\),装上一个物品 \(v\) ,所以权值为 \(0\)。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
const int maxn = 100 + 10 , maxm = 1e5 + 10;
const int INF = 0x3f3f3f3f;
int dis[maxm],n,a[maxn],P;
bool vis[maxm];
#define LL long long
#define read() read<int>()
#include <queue>
LL m;
void spfa(int s){
queue<int> q;
memset(dis,0x3f,sizeof dis);
memset(vis,false,sizeof vis);
dis[s]=0;q.push(s);vis[s]=true;
while(q.size()){
int u=q.front();q.pop();
vis[u]=false;
for(int i=1;i<n;i++){
int v=u+a[i],w=(v>=a[n])?(v-=a[n],0):1;//反悔过程
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v])q.push(v),vis[v]=true;
}
}
}
}
int main(){
int T=read();
while(T--){
m=read<LL>();n=read();
for(int i=1;i<=n;i++)a[i]=read();
sort(a+1,a+1+n);
spfa(0);
LL ans1=m/(LL)a[n];m%=(LL)a[n];
if(dis[m]>=INF)puts("IMPOSSIBLE");//拼凑剩余系
else printf("%lld\n",ans1+dis[m]);
}
return 0;
}
参考了标程代码。