luogu CF510D Fox And Jumping 裴蜀定理例题题解

 

Luogu Link

洛谷题解满了于是就来这里写了

大致题意

给出 n 张卡片,分别有 lici。在一条无限长的纸带上,你可以选择花 ci 的钱来购买卡片 i,从此以后可以向左或向右跳 li 个单位。问你至少花多少元钱才能够跳到纸带上全部位置。若不行,输出 1

题解

分析该问题,发现想要跳到每一个格子上,必须使得所选数 li1,,lik 通过数次相加或相减得出的绝对值为 1,也即存在整数 x1,,xn 使得 li1x1++likxk=1。由多个整数的裴蜀定理逆定理,这相当于 从数组 l1,,ln 选择若干个数,满足它们的最大公因数为 1,同时要求代价和最小

注:(给不熟悉裴蜀定理的人)

裴蜀定理:设 a,b 是不全为零的整数,则存在整数 x,y, 使得 ax+by=gcd(a,b).

多个整数裴蜀定理:设 a1,a2,,an 是不全为零的整数,则存在整数 x1,x2,,xn, 使得 a1x1+a2x2++anxn=gcd(a1,a2,,an)。其逆定理也成立:设 a1,a2,,an 是不全为零的整数,d>0a1,a2,,an 的公因数,若存在整数 x1,x2,,xn, 使得 a1x1+a2x2++anxn=d,则 d=gcd(a1,a2,,an)

好了,现在问题已经转化成了一个动态规划问题。如果看了刚才的定理有点忘了的话,那就再把这个问题重申一遍:

从数组 l1,,ln 选择若干个数,满足它们的最大公因数为 1,同时要求代价和 lici 最小。(li 是被选中的)

仔细一看——这是不是有点像 0-1 背包?

让我们来回顾一下普通的 0-1 背包求的是什么:在 (wi)<W 时,max(vi)。(i 是被选中的)

而对于这个问题,只不过是把价值改成了代价,又改了改限制条件而已

我们设 fi,j 表示考虑前 i 个数且 最大公因数为 j最小代价(注意与普通 0-1 背包对比,后者是设 fi,j 表示考虑前 i 个数且 和为 j最大价值),则有转移方程:

fi,j=mingcd(k,li)=jfi1,k+ci.

(请自行对比普通 0-1 背包转移方程以获得更深的理解)

DP 后最终的总代价即为 fn,1

然而这样的复杂度是 O(nai),会 TLE。

考虑一个简单的优化:即消去无用状态。

我们只需要开个 map,每次把访问过的 x 执行转移即可,这样就能轻松过题了。

事实上,这题用 unordered_map 应该更优,比较这题只需建立映射关系就行,不需要 map 那样的自动有序特性。

如果不想用 map 之类的 STL ,那么也可以如同一般的 0-1 背包问题,可以用滚动数组优化,去掉第一维。总之优化方法非常多,也都比较简单。

因此,这就是裴蜀定理+01背包+gcd=紫题

代码(可能有点抽象)

#include<bits/stdc++.h>
using namespace std;
const int N=305;
int n,l[N],c[N];
map<int,int> pre;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&l[i]);
    for(int i=1;i<=n;i++) scanf("%d",&c[i]);
    pre[l[1]]=c[1];
    for(int i=2;i<=n;i++)
    {
        for(auto k:pre) pre[__gcd(k.first,l[i])]==0?pre[__gcd(k.first,l[i])]=k.second+c[i]:pre[__gcd(k.first,l[i])]=min(pre[__gcd(k.first,l[i])],k.second+c[i]);
        pre[l[i]]=min((pre[l[i]]==0)?INT_MAX:pre[l[i]],c[i]);
    }
    printf("%d",pre[1]==0?-1:pre[1]);
}

十八行切掉的水紫