Comet OJ - Contest #10 鱼跃龙门 exgcd+推导
考试的时候推出来了,但是忘了 $exgcd$ 咋求,成功爆蛋~
这里给出一个求最小正整数解的模板:
ll solve(ll A,ll B,ll C) { ll x,y,g,b,ans; gcd = exgcd(A,B,x,y); if(C%gcd!=0) return -1; x*=C/gcd,B/=gcd; if(B<0) B=-B; ans=x%B; if(ans<=0) ans+=B; return ans; }
大概就是这样.
说一下题:
可以将题目转化成求 $\frac{ans(ans+1)}{2}\mod n=0$ 的最小 $ans$.
将式子转化一下,即 $ans(ans+1)=2n\times y$,其中 $y$ 是个整数.
易得 $ans$ 与 $ans+1$ 是互质的,所以 $2n$ 中每一种不同的质因子只能贡献给 $ans,ans+1$ 中的一个.
而 $10^{12}$ 以内的数字最多只会有不到十多个本质不同的质因子,所以可以枚举子集.
考虑枚举出 $A$ 和 $B$,令 $A\times x=ans,B\times y=ans+1$.
则需要满足 $Ax-By=-1,gcd(x,y)=1$
但其实我们发现 $gcd(x,y)=1$ 是不用判的,因位如果等式成立,则 $gcd(x,y)$ 就一定是 $1$.
那么,我们只需找到 $Ax-By=-1$ 的最小 $x$ 正整数解就行了.
这个可以用 $exgcd$ 直接求,但是有一些细节需要注意:
- $A,B$ 都需要大于 $0$.
- 我们想求 $x$,所以 $y$ 到底是多少我们是不关心的,直接无视掉就好.
Code:
#include <cstdio> #include <vector> #include <algorithm> #define N 1000006 #define inf 100000000000000000 #define ll long long #define LL long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; ll exgcd(ll a,ll b,ll &x,ll &y) { if(b==0) { x=1,y=0; return a; } ll ans=exgcd(b,a%b,x,y); ll tmp=x; x=y,y=tmp-a/b*y; return ans; } vector<ll>v; int tot; int prime[N],is[N]; void init() { int i,j; for(i=2;i<N;++i) { if(!is[i]) prime[++tot]=i; for(j=1;j<=tot&&1ll*prime[j]*i<1ll*N;++j) { is[prime[j]*i]=1; if(i%prime[j]==0) break; } } } ll solve(ll A,ll B) { ll x,y,g,b,ans; g = exgcd(A,B,x,y); if(-1%g) return inf; x*=-1/g,y*=g; B/=g; if(B<0) B=-B; ans=x%B; if(ans<=0) ans+=B; return ans*A; } int main() { // setIO("input"); init(); int T,i,j; scanf("%d",&T); while(T--) { ll n,h,answer=inf; scanf("%lld",&n),h=n; for(i=1;i<=tot;++i) { if(h%prime[i]==0) { ll kk=1; while(h%prime[i]==0) { h/=prime[i]; kk*=prime[i]; } v.push_back(kk); } } if(h) v.push_back(h); int len=v.size(); for(i=0;i<(1<<len);++i) // 枚举所有子集 { ll tmp=1; for(j=0;(1<<j)<=i;++j) { if(i&(1<<j)) tmp*=v[j]; } ll A=tmp,B=2*n/tmp; answer=min(answer,min(solve(A,B),solve(B,A))); } printf("%lld\n",answer); v.clear(); } return 0; }