题解 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 值不同,主要区别在于循环变量的递增时机和条件判断逻辑:

  1. for 循环版本

    • 执行顺序:i=Mcheck(i)i++check(i)...
    • 先检查当前值再递增:每次循环时先判断 i 的当前值是否满足条件,再执行 i++
    • check(M) 为真,直接输出 M 并终止,不会执行 i++
  2. 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 循环完全一致。

总结归纳

就这样吧。笔者想说的说完了

posted @ 2025-08-04 19:55  枯骨崖烟  阅读(7)  评论(0)    收藏  举报