HIT2018春校赛 I题 略解

题目描述如下:甲乙丙丁四人游戏,甲有二整数 X 和 Y,四人已知 2 <= X <= Y <= N,但乙丙丁未知 X 和 Y 确值。现在,甲告诉乙 X + Y 的值,甲告诉丙 X * Y 的值,下面四人对话:
乙:我不知道 X 和 Y 的确值,但我知道丙不知道 X 和 Y 的确值;
丙:我原不知道 X 和 Y 的确值,但我现在知道 X 和 Y 的确值了;
乙:我现在也知道 X 和 Y 的确值了;
丁:我现在也知道 X 和 Y 的确值了。

要求给定 N,判断这些话是否能被同时满足,输出 YES 或 NO。T 组数据,1 <= T <= 200,2 <= N <= 3000。

据说此题为 HIT2018春校赛I题,真实性不论,本题还是比较有意思的。可以注意到,四人中只有丁和解题者是处于同一信息状态下的。换句话说,若丁最后确实知道了 X 和 Y 的确值,那么我们也可以知道 X 和 Y 的确值。设初始解集 P = { ( X , Y ) ∣ 2 < = X < = Y < = N , X ∈ N , Y ∈ N } P = \{(X, Y) | 2 <= X <= Y <= N, X \in \mathbb{N}, Y \in \mathbb{N}\} P={(X,Y)2<=X<=Y<=N,XN,YN},下面就根据对话逐步缩小解集 P,直至仅剩余一个元素,即为解。若剩余多个元素或解集为空,即条件不能被同时满足。

对于第一句前半乙的话和第二句前半丙的话,我们可以初步缩小解集。将解集 P 中数对 (X, Y) 按 X * Y 的值划为不超过 N ∗ N N * N NN 个数簇,X * Y 的值相同的划为同一数簇,不同的划为不同数簇。若某一数簇内只有一个数对,则该数对必不为解,从解集中除去。这样处理后的解集 P 中所有数对可以使得第二句前半丙的话为真。对于第一句前半乙的话则可以简化处理,6 <= X + Y <= N + N - 2 即可。

接下来处理第一句后半乙的话,对于任一 S ∈ [ 6 , N + N − 2 ] S \in [6, N + N - 2] S[6,N+N2],将解集 P 中所有 X + Y = S 的数对 (X, Y) 取出,构成子集 Q S Q_S QS。换句话说,取集合 Q S ∈ P Q_S \in P QSP 使得 ∀ ( X , Y ) ∈ Q S , X + Y = S \forall (X, Y) \in Q_S, X + Y = S (X,Y)QS,X+Y=S 并且 ∀ ( X , Y ) ∈ P − Q S , X + Y ≠ S \forall (X, Y) \in P - Q_S, X + Y ≠ S (X,Y)PQS,X+Y=S。对于任一 Q S Q_S QS,若 ∃ ( X , Y ) ∈ Q S \exist (X, Y) \in Q_S (X,Y)QS 使得 (X, Y) 所在数簇仅有一个数对,则集合 Q S Q_S QS 内所有数对必不为解,从解集中除去。这样处理后的解集 P 中所有数对可以使得第一句后半乙的话为真。

对于第二句后半丙的话,根据当前解集 P 重新构建数簇,若某一数簇内有超过一个数对,则该数簇内所有数对必不为解,从解集中除去。对于第三句乙的话,根据当前解集 P 重新构建 Q S Q_S QS,若某一 Q S Q_S QS 内有超过一个数对,则该 Q S Q_S QS 内所有数对必不为解,从解集中除去。

最后,若解集 P 中仅剩余一个元素,则丁和我们均知道了 X 和 Y 的确值,否则这些话不能被同时满足。这样,我们得到了一个朴素的算法。

代码如下:

#include<stdio.h>
#define MAX_N2 (9000005)
#define MAX_N22 (5000000)
struct pr
{
	int x,y;
}p[MAX_N22];
int cnt[MAX_N2];
bool fgr(int n)
{
	bool flg;
	int m=0;
	for(int i=4;i<=n*n;i++)
		cnt[i]=0;
	for(int i=2;i<=n;i++)
		for(int j=i;j<=n;j++)
			cnt[i*j]++;
	for(int i=6;i<=n+n-2;i++)
	{
		flg=true;
		for(int j=2;flg&&j+j<=i;j++)
			if(cnt[j*(i-j)]<=1)
				flg=false;
		for(int j=2;flg&&j+j<=i;j++)
			p[m].x=j,p[m++].y=i-j;
	}
	if(m==0)
		return false;
	for(int i=4;i<=n*n;i++)
		cnt[i]=0;
	for(int i=0;i<m;i++)
		cnt[p[i].x*p[i].y]++;
	for(int i=0;i<m;i++)
		if(cnt[p[i].x*p[i].y]>1)
			p[i].x=p[--m].x,p[i--].y=p[m].y;
	if(m==0)
		return false;
	for(int i=4;i<=n+n;i++)
		cnt[i]=0;
	for(int i=0;i<m;i++)
		cnt[p[i].x+p[i].y]++;
	for(int i=0;i<m;i++)
		if(cnt[p[i].x+p[i].y]>1)
			p[i].x=p[--m].x,p[i--].y=p[m].y;
	return m==1;
}
int main()
{
	int n,t;
	scanf("%d",&t);
	while(t--)
		scanf("%d",&n),
		puts(fgr(n)?"YES":"NO");
	return 0;
}

很明显,上面的代码并不能在时间限制内跑完,需要优化。最容易想到的是打表,实施后发现跑完 2 到 3000 所有数据花费时间不超过一分钟。不过这使得代码略长,于是考虑一个问题:使得所有话成立的 N 是否构成了一个整数连续的区间?当 N 很小的时候,作为解的数对不在初始解集里;当 N 充分大的时候,原本成立的解变得不成立或不唯一了。但是,也可能随 N 增大而产生新解。也就是说,从理论上不能证明使得解成立的 N 取值为一个整数区间。但事实上,观察打出的表可以得出,使得数对成立的 N 构成了一个区间,该区间为 [62, 865],当 N 取值为该区间内整数时,存在唯一数对解 (4, 13)。

优化代码如下:

#include<stdio.h>
int main()
{
	int n,t;
	scanf("%d",&t);
	while(t--)
		scanf("%d",&n),
		puts(n>61&&n<866?"YES":"NO");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值