裴蜀定理学习记录
观察到2x-y可以拆成x+(x-y),现在模拟一下这个过程
发现得到的数可以看成从某个点xj出发,加上若干个两数之间的差的形式。
再考虑一下2x-y的几何意义,发现相当于在数轴上做x关于y的对称点,并且和数的分布位置有关,和具体数值是无关的
接下来有一个不太好想的key point:
设S为无限次操作后能得到的数的集合
如果存在0 ∈ S,那么如果x ∈ S,则 nx ∈ S .........①
又因为可以平移,我们先让所有ai和k一起减去a[1](这里a[1]为a中其他值都一样,只是为了凑出0)
则得到 a1-a1( =0 ),a2-a1,a3-a1...... an-a1,令新得到的数组为a'。
现在问题变成从0出发,每次可以加任意两数的差的绝对值,问是否能得到k-a1。
如果直接暴力处理出所有两数的差,数量是O(n^2)的
但是发现做完差分( a2'-a1',a3'-a2',a4'-a3',a5'-a4' ...)之后,任意两数的差ai'-aj'都可以由(ai'-ai-1')+(ai-1'-ai-2')+...(aj+1'-aj')得到
中间那部分可以消掉,写成 ai'+(-ai-1'+ai-1')+(-ai-2'+ai-2')...(-aj+1'+aj+1')-aj'=ai'-aj'
这是i>j的情况,而i<j的情况可以直接乘个-1,依据是①
也就是我们用O(n)级别的两数差表达了O(n^2)的情况,根据裴蜀定理,问题转化成 k-a1 | gcd( a2'-a1',a3'-a2',a4'-a3',a5'-a4' ...)能否成立
注:裴蜀定理指的是ax+by=k若有解要求k | gcd(a,b),可以推广到任意多项,证明见:裴蜀定理 - OI Wiki (oi-wiki.org)
#include<bits/stdc++.h> using namespace std; #define int long long const int N = 2e5+5; int a[N]; void solve(){ int n,k;cin>>n>>k; for(int i=1;i<=n;i++) cin>>a[i]; k-=a[1]; for(int i=2;i<=n;i++) a[i]-=a[1]; a[1]-=a[1]; int g=0; for(int i=2;i<=n;i++){ g=__gcd(g,abs(a[i]-a[i-1])); } if(k%g==0){ cout<<"YES"<<"\n"; } else cout<<"NO"<<"\n"; } signed main(){ int t;cin>>t; while(t--){ //TODO solve(); } }
问题转化为挑出若干个li使得它们的值为gcd(根据裴蜀定理,它们可以线性组合出所有整数),且 Σci 最小
经典背包,dp[i][j] 表示当前考虑到第i个物品,选出来的 li 的gcd值为mp[j]的最小代价
n<=300,所以它们的gcd组合数目不会很多,但又可能很大,解决办法是开一个unordered_map存储,mp[i]表示第i个gcd的值
转移就是经典背包