[题解] 背包(同余)

[题解] 背包(同余)

背包

题意

给你 \(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]\) 进行扩展,对于边权分一下两种情况:

  1. \(v< P\),边权为 \(1\)

  2. \(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;
}

参考了标程代码。

posted @ 2021-08-12 17:33  ¶凉笙  阅读(60)  评论(0编辑  收藏  举报