一、问题引入
当你想要解决一个完全背包计数问题,但是 \(M\) 的范围太大,那么你就可以使用同余最短路。
二、算法推导过程
首先对于一个完全背包计数问题,我们要知道如果 \(x\) 这个数能凑出来,那么 \(x+a_i,x+2a_i,x+3a_i,\dots\) 一定都能凑出来,所以说,我们随便找一个 \(a_i\),找到对于 \([0,a_i-1]\) 中的每个数 \(k\) 的最小的能凑出来的数并且这个数模 \(a_i\) 等于 \(k\),这个时候从最小的可以想到最短路,于是我们就有了一个大胆的想法,首先为了节省时间复杂度,把模数设定成最小的 \(a_i\),然后进行最短路,最短路的过程就是不断尝试增加其它的 \(a_i\),然后再取模,你会发现,这是对的!哦对,刚刚只是找到了对于 \([0,a_i-1]\) 中的每个数 \(k\) 的最小的能凑出来的数并且这个数模 \(a_i\) 等于 \(k\),统计的话还要统计能加多少次 \(a_i\)。
三、同余最短路模板
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = ;//数据范围
int a[N];
int f[N];
int vis[N];
struct node
{
int x;
int w;
bool operator<(const node&a)const
{
return w>a.w;
}
};
signed main()
{
int n;
long long m;
scanf("%d %lld",&n,&m);
for(int i = 1;i<=n;i++)
{
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
memset(f,0x3f,sizeof(f));
f[0] = 0;
priority_queue<node>q;
q.push({0,0});
while(q.size())
{
node x = q.top();
q.pop();
if(vis[x.x])
{
continue;
}
vis[x.x] = 1;
for(int i = 2;i<=n;i++)
{
int v = (x.x+a[i])%a[1];
if(f[v]>f[x.x]+a[i])
{
f[v] = f[x.x]+a[i];
q.push({v,f[v]});
}
}
}
long long ans = 0;
for(int i = 0;i<a[1];i++)
{
if(f[i]<=m)
{
ans+=(m-f[i])/a[1]+1;
}
}
printf("%lld",ans-1);
return 0;
}
注意:这只是一个板子,应用时请随机应变。
四、同余最短路例题
U553673 硬币问题
同余最短路模板题,代码放上供参考:
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int a[N];
long long f[N];
int vis[N];
struct node
{
int x;
int w;
bool operator<(const node&a)const
{
return w>a.w;
}
};
signed main()
{
int n;
long long m;
scanf("%d %lld",&n,&m);
for(int i = 1;i<=n;i++)
{
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
memset(f,0x3f,sizeof(f));
f[0] = 0;
priority_queue<node>q;
q.push({0,0});
while(q.size())
{
node x = q.top();
q.pop();
if(vis[x.x])
{
continue;
}
vis[x.x] = 1;
for(int i = 2;i<=n;i++)
{
int v = (x.x+a[i])%a[1];
if(f[v]>f[x.x]+a[i])
{
f[v] = f[x.x]+a[i];
q.push({v,f[v]});
}
}
}
long long ans = 0;
for(int i = 0;i<a[1];i++)
{
if(f[i]<=m)
{
ans+=(m-f[i])/a[1]+1;
}
}
printf("%lld",ans-1);
return 0;
}
结果稀里糊涂地拿到了最优解(不要看最快的提交,因为数据很水然后我没开 long long 结果过了,所以看第二快的提交)......
P3403 跳楼机
只需要把 \(m\) 减一(因为题目是从第 \(1\) 层开始,然而我们的代码是从第 \(0\) 层),然后正常套模板就行了。
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int a[N];
long long f[N];
int vis[N];
struct node
{
int x;
long long w;
bool operator<(const node&a)const
{
return w>a.w;
}
};
signed main()
{
int n = 3;
long long m;
scanf("%lld",&m);
m--;
for(int i = 1;i<=n;i++)
{
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
memset(f,0x3f,sizeof(f));
f[0] = 0;
priority_queue<node>q;
q.push({0,0});
while(q.size())
{
node x = q.top();
q.pop();
if(vis[x.x])
{
continue;
}
vis[x.x] = 1;
for(int i = 2;i<=n;i++)
{
int v = (x.x+a[i])%a[1];
if(f[v]>f[x.x]+a[i])
{
f[v] = f[x.x]+a[i];
q.push({v,f[v]});
}
}
}
long long ans = 0;
for(int i = 0;i<a[1];i++)
{
if(f[i]<=m)
{
ans+=(m-f[i])/a[1]+1;
}
}
printf("%lld",ans);
return 0;
}
由于作者水平不行,所以只能做这些题,后面还会有更多例题,敬请期待!
转圈背包由于太难,作者理解之后再更新!
但是似乎我发现转圈背包虽然理论上比同余最短路快,但是实际上效率远不如同余最短路,特别是在大数据,这是因为迪杰斯特拉在不被卡的情况下时间复杂度比 \(O(E \log V)\) 快得多。