洛谷 P1516 青蛙的约会

算是手推了一次数论题,以前做的都是看题解,虽然这题很水而且还交了5次才过。。。

求解方程\(x+am\equiv y+an \pmod l\)中,\(a\)的最小整数解

\(0<x\neq y\leq 2\cdot 10^9,0<n,m\leq 2\cdot 10^9,0<l\leq 2.1\cdot 10^9\)


做一下变形:

\[x-y\equiv a(n-m) \pmod l
\]

设\(w=x-y,r=n-m\),则

\[ar\equiv w \pmod l
\]

此时,设\(\gcd(l,r)=gcd\),那么可以分两种情况

如果\(w\)是\(gcd\)的倍数,则原式可以写为:

\[ar'gcd\equiv w'gcd \pmod{l'gcd}
\]

根据同余式的性质,可以将\(gcd\)化简掉,具体证明在这里

\[ar'\equiv w'\pmod l'
\]

此时,\(\gcd(r',l')=1\),那么可以求出\(r' \bmod l'\)的逆元

那么\(w'r'^{-1}\bmod l'\)即为答案

具体操作就是用 exgcd,求出一个\(qr+pl=\gcd(r,l)\),然后可以表示为\(qr'gcd+pl'gcd=gcd\)

\(gcd\)被约掉,就是\(qr'+pl'=1\),则\(q\)即为\(r'\bmod l'\)的逆元

还有一种情况\(w\)不是\(gcd\)的倍数,显然无解

再就是注意\(w,r\)会不会出现负数,如果有负数在取模的时候可能会出现一些玄学情况,要现在\(\bmod l\)下把他们变成正的

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline LL read(){
LL x=0,y=1;
char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
LL exgcd(LL &x,LL &y,LL a,LL b){
if(!b) return x=1,y=0,a;
LL gcd=exgcd(x,y,b,a%b);
int xx=x;x=y;
y=xx-a/b*y;
return gcd;
}
int main(){
LL n,m,x,y,l;
x=read();y=read();m=read();n=read();l=read();
if(m==n) return std::puts("Impossible"),0;
LL r=n-m,w=x-y;
if(r<0) r=(r+((-r/l)+1)*l)%l;
if(w<0) w=(w+((-w/l)+1)*l)%l;
LL a,b;
int gcd=exgcd(a,b,r,l);
if(w%gcd) return std::puts("Impossible"),0;
l/=gcd;w/=gcd;
std::printf("%lld",(w*a%l+l)%l);
return 0;
}

 

洛谷 P2421 [NOI2002]荒岛野人LOJ上也有

这题和上面那个几乎一样,代码复制过去改一改加个主函数就行

克里特岛以野人群居而著称。岛上有排列成环行的\(M\)个山洞。这些山洞顺时针编号为 \(1,2,\dots,M\)

岛上住着\(n\)个野人,一开始依次住在山洞 \(C_1,C_2,\dots,C_n\)中。以后每年,第 \(i\) 个野人会沿顺时针向前走 \(P_i\)个洞住下来。

每个野人\(i\)有一个寿命值 \(L_i\),即生存的年数。

奇怪的是,虽然野人有很多,但没有任何两个野人在有生之年处在同一个山洞中,使得小岛一直保持和平与宁静,这让科学家们很是惊奇。他们想知道,至少有多少个山洞,才能维持岛上的和平呢?数据保证有解,\(M\) 的值不大于\(10^6\)

这是真正的数据范围,洛谷上标的很乱:\(0<C_i,P_i\leq 100,1\leq N\leq 15,0\leq L_i\leq 10^6\)


把题意描述成数学语言,就是求一个最小的\(M\),使得对于任意的\(i\neq j,c_i+kp_i\equiv c_j+kp_j \pmod M\)无解,或解大于\(\min(L_i,L_j)\)

然后这个式子就和上面那个题一模一样了,就是记得判断解的大小就行

一开始想二分处理,然而发现并不满足可二分性,但保证\(M\leq 10^6\),很小,而且又保证有解,所以可以从\(\max c_i\)开始枚举\(M\),枚举到成立就行了

复杂度\(O(Mn^2\log C_i)\),超过\(10^9\)了,而且这还是02年的题,那时候恐怕测评机比现在也慢

不过肯定是跑不满,也不太可能会有什么毒瘤数据,估计是没有更优做法了

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
register int x=0;register int y=1;
register char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
int exgcd(int &x,int &y,int a,int b){
if(!b) return x=1,y=0,a;
LL gcd=exgcd(x,y,b,a%b);
int xx=x;x=y;
y=xx-a/b*y;
return gcd;
}
inline int check(int m,int n,int x,int y,int l,int min){//calc am+x = an+y mod l
if(m==n) return 0;
LL r=n-m,w=x-y;
if(r<0) r=(r+((-r/l)+1)*l)%l;
if(w<0) w=(w+((-w/l)+1)*l)%l;
int a,b;
int gcd=exgcd(a,b,r,l);
if(w%gcd) return 0;
w/=gcd;l/=gcd;
if((w*a%l+l)%l>min) return 0;
return 1;
}
int c[19],p[19],l[19];
int main(){
int n=read(),maxc=0;
for(reg int i=1;i<=n;i++) c[i]=read(),p[i]=read(),l[i]=read(),maxc=std::max(maxc,c[i]);
for(reg int m=maxc;;m++){
for(reg int i=1;i<=n;i++)
for(reg int j=i+1;j<=n;j++)
if(check(p[i],p[j],c[i],c[j],m,std::min(l[i],l[j]))) goto FAIL;
return std::printf("%d",m),0;
FAIL:;
}
return 0;
}
05-02 00:29