同余最短路
同于最短路可以理解为完全背包问题扩展,形如:
- 背包总体积非常大,不支持枚举;
- 每个物品的体积比较小,满足 \(n \times v_i \le 10^5\),支持枚举;
- 只是判定而不要求最优
注意完全背包,多重背包是做不了的。
但是可以做类多重背包(转移一个等差数列),这个是加很多个等差数列中的几项。
可行性判定转最优性求解应该是优化思路。
考虑对于某一个物品,我们求出以他为模数情况下,剩余系背包的最小体积。
具体的,选定某个物品体积做模数,设为 \(B\)。
设 \(f[j]\) 表示凑出体积 \(\equiv j(mod B)\) 所需要的最小体积。
转移是:
\[f_{(j+v)modB}=min(f_{(j+v)modB},f_j+v)
\]
可以轻松转化为图论问题然后求最短路即可。
不过有一种更优雅的转圈做法。
当一个体积为 \(v\) 的物品在 \(B\) 为基数发生转移的时候。
根据著名题目 《最小环》 的结论,会形成 \(gcd(v,B)\) 个互不相交的转移环,几个环的转移不相干。
破环成链后,即可使得每个点都可以转移到所有点上,因此等价于转两圈即可,给出模板:
for(int i=1,x;i<n;i++)
{
cin>>x;
for(int j=0,lim=__gcd(x,m);j<lim;j++)
{
for(int k=j,c=0;c<2;c+=(k==j),k=(k+x)%m)
{
int t=(k+x)%m;
f[t]=min(f[t],f[k]+x);
}
}
}
Tricks
-
转两圈可以变成转一圈,注意到环上初始代价最小的点一定不会被更新,因此以他为起点一定正确。
-
对于最优化问题,可以考虑一定的贪心策略,将基数特殊化
考虑对比两组背包方案的优劣是具体的写出每个背包方案的估价函数。
在转移的时候更新加入一次该物品造成的代价。
- 更换基数:
先将dp值换模数,具体而言就是 \(f[g_j mod nB]min=g_j\)
然后再将原来的基数作为一个物品进行转移:
点击查看代码
void trans(int x)
{
memcpy(g,f,sizeof f);
for(int j=0;j<x;j++) f[j]=Inf;
for(int j=0;j<B;j++) f[g[j]%x]=min(f[g[j]%x],g[j]);
for(int j=0,lim=__gcd(x,B);j<lim;j++)
for(int k=j,c=0;c<2;k=(k+B)%x,c+=(k==j))
f[(k+B)%x]=min(f[(k+B)%x],f[k]+B);
B=x;
}
- 转移等差数列
首先将基数改成首项,依然是对每一个转移环依次处理,然后在只增加一个首项的情况下可以从第 \(i-len\) 项转移过来。
设从环上第 \(j\) 个位置转移过来,代价为 \((i-j)\times diff + first\),拆贡献后单调队列转移
注意此时找到环上最小值作为起点再转移更方便。
点击查看代码
void update(int x,int d,int len)
{
trans(x);
if(!d) return;
for(int j=0,lim=__gcd(x,d);j<lim;j++)
{
int Minp=j,tot=0;
for(int k=j,c=0;c<1;k=(k+d)%x,c+=(k==j)) if(f[k]<f[Minp]) Minp=k;
for(int k=Minp,c=0;c<1;k=(k+d)%x,c+=(k==Minp)) seq[++tot]=k;
int hh=0,tt=-1;
q[++tt]=1;
for(int i=2;i<=tot;i++)
{
while(hh<=tt && q[hh]<i-len) hh++;
if(hh<=tt) f[seq[i]]=min(f[seq[i]],f[seq[q[hh]]]+(i-q[hh])*d+x);
while(hh<=tt && f[seq[i]]-i*d <= f[seq[q[tt]]]-q[tt]*d) tt--;
q[++tt]=i;
}
}
}

浙公网安备 33010602011771号