题解 luogu.P2421 [NOI2002] 荒岛野人
题目
luogu.P2421 [NOI2002] 荒岛野人
一道比较有意思的题,主要是有一些细节的错误犯了。再者就是这个题型比较经典,所以拿来稍做分析。
题意建模
原题转化过来就是判断一个同余方程是否有解,也就是如下的:
\(C_{i}+y·P_{i} \equiv C_{j}+y·P_{j}\ (\mod m\ ), i,j \in [1,n]\)
其中,我们的任务是找到一个最小的 \(m\),使得上面的同余方程无解。
注意,这里的无解指的是:
- 方程无解;
- \(y\le \min(L_{i},L_{j})\)
所以需要设计一个判断的 check 函数。
我们看代码。
算法分析
这就不用分析了吧,题意非常明显了吧。
参考代码
#include<iostream>
using namespace std;
const int N=20;
int n,c[N],p[N],l[N];
int exgcd(int a,int b,int &x,int &y)
{
int gcd;
if(!b) { x=1,y=0; return a; }
gcd=exgcd(b,a%b,y,x);
y-=a/b*x;
return gcd;
}
inline bool check(int m)
{//true为可行解,false为不可行解
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
int x,y;
int a=m,b=p[j]-p[i],d=c[i]-c[j];
int gcd=exgcd(a,b,x,y);
if(d%gcd) continue;
a/=gcd, b/=gcd, d/=gcd;
if(a<0) a=-a;
y=(y*d%a+a)%a;
if(y<=min(l[i],l[j])) return false;
}
return true;
}
int main()
{
cin>>n;
int M=0;
for(int i=1;i<=n;i++)
{
cin>>c[i]>>p[i]>>l[i];
M=max(c[i],M);
}
for(int i=M;;i++)
if(check(i))
{
cout<<i<<'\n';
return 0;
}
return 0;
}
细节实现
自己敲着个代码的时候,突发奇想,把这个非常冒险的枚举(依赖 check 函数返回值的循环手动改写了一下),结果就爆掉了。手动模拟了一下,但还是没有想出来哪里有问题。所以这边就去查了一下资料。
for(int i=M;;i++)
if(check(i))
{
cout<<i<<'\n';
return 0;
}
while(M++)
if(check(M))
{
cout<<M<<'\n';
return 0;
}
这两段代码最终输出的 M 值不同,主要区别在于循环变量的递增时机和条件判断逻辑:
-
for循环版本- 执行顺序:
i=M→check(i)→i++→check(i)... - 先检查当前值再递增:每次循环时先判断
i的当前值是否满足条件,再执行i++。 - 若
check(M)为真,直接输出M并终止,不会执行i++。
- 执行顺序:
-
while循环版本- 执行顺序:
check(M)→M++→check(M)... - 先递增再检查:
while(M++)会在判断条件时先执行M++,再检查check(M-1)的结果。 - 若
check(M)为真,实际输出的是M+1(因为M++已先执行)。
- 执行顺序:
关键差异总结
| 循环类型 | 检查的值 | 递增时机 | 输出结果 |
|---|---|---|---|
for |
当前 i |
循环体结束后 | 首次满足条件的 i |
while |
M++ 后的新值 |
条件判断时即递增 | 首次满足条件的 M+1 |
等效改写建议
若需两者输出相同结果,可调整 while 循环为:
while(true) {
if(check(M)) {
cout << M << '\n';
break;
}
M++;
}
此时逻辑与 for 循环完全一致。
总结归纳
就这样吧。笔者想说的说完了

浙公网安备 33010602011771号