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,XN,YN}P = \{(X, Y) | 2 <= X <= Y <= N, X \in \mathbb{N}, Y \in \mathbb{N}\},下面就根據對話逐步縮小解集 P,直至僅剩餘一個元素,即爲解。若剩餘多個元素或解集爲空,即條件不能被同時滿足。

對於第一句前半乙的話和第二句前半丙的話,我們可以初步縮小解集。將解集 P 中數對 (X, Y) 按 X * Y 的值劃爲不超過 NNN * N 個數簇,X * Y 的值相同的劃爲同一數簇,不同的劃爲不同數簇。若某一數簇內只有一個數對,則該數對必不爲解,從解集中除去。這樣處理後的解集 P 中所有數對可以使得第二句前半丙的話爲真。對於第一句前半乙的話則可以簡化處理,6 <= X + Y <= N + N - 2 即可。

接下來處理第一句後半乙的話,對於任一 S[6,N+N2]S \in [6, N + N - 2],將解集 P 中所有 X + Y = S 的數對 (X, Y) 取出,構成子集 QSQ_S。換句話說,取集合 QSPQ_S \in P 使得 (X,Y)QS,X+Y=S\forall (X, Y) \in Q_S, X + Y = S 並且 (X,Y)PQS,X+YS\forall (X, Y) \in P - Q_S, X + Y ≠ S。對於任一 QSQ_S,若 (X,Y)QS\exist (X, Y) \in Q_S 使得 (X, Y) 所在數簇僅有一個數對,則集合 QSQ_S 內所有數對必不爲解,從解集中除去。這樣處理後的解集 P 中所有數對可以使得第一句後半乙的話爲真。

對於第二句後半丙的話,根據當前解集 P 重新構建數簇,若某一數簇內有超過一個數對,則該數簇內所有數對必不爲解,從解集中除去。對於第三句乙的話,根據當前解集 P 重新構建 QSQ_S,若某一 QSQ_S 內有超過一個數對,則該 QSQ_S 內所有數對必不爲解,從解集中除去。

最後,若解集 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章