……

解题报告: luogu P6476&NOIOR2 T1

一种新的解法,对数论能力要求低!
考场上推错式子了,只有 25 分.....

题目链接:P6476 [NOI Online #2 提高组]涂色游戏(民间数据)

声明:此文中用 \(x,y\) 代表 \(p1,p2\)\((x,y)\)\(\gcd(x,y)\)当然还可能是开区间,\([x,y]\)\(lcm(x,y)\)当然还可能是闭区间,并满足 \(x<y\)

我们做这个题的第一反应一定是这样的:
每隔一个 \(y\) 倍数的区间都有 \(\left\lfloor\dfrac{y}{x}\right\rfloor\)\(x\) 的倍数出现,然后和 \(k\) 比大小即可。
然而被无情 hack 了,such as:

输入:
1
5 9 2
输出:
NO

我们发现在 \((9,18)\) 中有 \(2\)\(x\) 的倍数:\(10\)\(15\)

那我们可以利用一个规律:
\(y\)\(x\) 没有整除关系时,每一段 \(y\)\(x\) 倍数出现次数 \(\in\{\left\lfloor\dfrac{y}{x}\right\rfloor,\left\lfloor\dfrac{y}{x}\right\rfloor+1\}\)
为什么呢?
我们设 \(z=\left\lfloor\dfrac{y}{x}\right\rfloor\)
首先不可能更少的次数,因为 \((0,y)\) 中浪费最少了。
假如存在更多的次数,比如 \(z+2\)
中间的空隙算上就已经 \(>y\) 了。
显然得证。
所以最上面的解法只存在一种可能:
我们设第一次\(y\) 区间内出现 \(z+1\)\(x\) 倍数的区间为\((my,(m+1)y)\),其中的 \(x\) 分别为 \(kx,(k+1)x......(k+z)x\)
那么这个区间是样的:

(字丑不要介意啦quq
此时显然满足:

\[\begin{cases}my<kx\\(m+1)y>(k+z)x\end{cases} \]

我们注意:
我们定义的数都是第一次出现这种情况的,所以显然:

\[k=mz+1 \]

结合一下:

\[\begin{cases}my<(mz+1)x\\(m+1)y>(mz+z+1)x\end{cases} \]

容易解得:

\[\begin{cases}m<\dfrac{x}{y-zx}\\\\m>\dfrac{(z+1)x-y}{y-zx}\end{cases} \]

显然在 \(y-zx=0\)\(x|y\) 是无意义,我们需要特判一下,循环节只有一个有 \(z-1\) 个数,这时候直接判断即可。

这就完了吗?
并没有,我们显然发现每个 \(y\) 区间的 \(x\) 倍数出现数在每个 \([x,y]\) 长的区间都有循环节。
那么显然:

\[(m+1)y\leqslant [x,y]=\dfrac{xy}{(x,y)} \]

容易得到:

\[m\leqslant\dfrac{x}{(x,y)}-1 \]

也就是:

\[m<\dfrac{x}{(x,y)} \]

还有一个条件:

\[m>0 \]

那么问题转化成了:
是否存在整数 \(m\) 满足:

\[m\in(\max\{0,\dfrac{(z+1)x-y}{y-zx}\},\min\{\dfrac{x}{(x,y)},\dfrac{x}{y-zx}\}) \]

这些过程是可以直接模拟的。
代码如下:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;

#define read(x) scanf("%d",&x)

int t,x,y,z,k;
double l,r;
int g;

inline int gcd(int a,int b){return b==0?a:gcd(b,a%b);}

int check(double l,double r)//判断区间是否有正整数
{
	if(l>r) return 0;
	l=ceil(l),r=floor(r);
	if(l<=r) return 1;
	else return 0;
}

int main()
{
	read(t);
	while(t--)
	{
		read(x),read(y),read(k);
		if(x>y) swap(x,y);
		if(k==1){printf("NO\n");continue;}
		else if(y%x==0)
		{
			z=y/x-1;
			if(k<=z){printf("NO\n");continue;}
			else{printf("YES\n");continue;}
		}
		else
		{
			z=y/x,g=gcd(x,y),g=x/g;
			if(k<=z){printf("NO\n");continue;}
			else if(k==z+1)
			{
				l=max((double)0,((double)(z+1)*(double)x-(double)y)/(y-z*x));
				r=min(double(g),(double)x/(y-z*x));
				if(check(l,r)){printf("NO\n");continue;}
				else printf("YES\n");
				continue;
			}
			else{printf("YES\n");continue;}
		}
	}
	return 0;
}

然而这样只有 \(85\;pts\),为啥呢?
因为我们要判断的区间有可能是这个亚子:

\[(5,6) \]

我们需要一个小 trick ,运用极限思想,把区间改成这样:

\[(5+eps,6-eps) \]

就可以 \(AC\) 了。
总的来说,这种做法比小学奥数还小学奥数,只要够细致就可以得到答案。
最后附上 \(AC\) 代码:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;

#define read(x) scanf("%d",&x)

int t,x,y,z,k;
long double l,r;
int g;

inline int gcd(int a,int b){return b==0?a:gcd(b,a%b);}

int check(double l,double r)
{
	if(l>r) return 0;
	l+=0.0000001,r-=0.0000001;
	l=ceil(l),r=floor(r);
	if(l<=r) return 1;
	else return 0;
}

int main()
{
	read(t);
	while(t--)
	{
		read(x),read(y),read(k);
		if(x>y) swap(x,y);
		if(k==1){printf("NO\n");continue;}
		else if(y%x==0)
		{
			z=y/x-1;
			if(k<=z){printf("NO\n");continue;}
			else{printf("YES\n");continue;}
		}
		else
		{
			z=y/x,g=gcd(x,y),g=x/g;
			if(k<=z){printf("NO\n");continue;}
			else if(k==z+1)
			{
				l=max((double)0,((double)(z+1)*(double)x-(double)y)/(y-z*x));
				r=min(double(g),(double)x/(y-z*x));
				if(check(l,r)){printf("NO\n");continue;}
				else printf("YES\n");
				continue;
			}
			else{printf("YES\n");continue;}
		}
	}
	return 0;
}
posted @ 2020-04-29 22:58  童话镇里的星河  阅读(262)  评论(0编辑  收藏  举报