[BJOI2018] 双人猜数游戏 题解

文章详细分析了一个多人游戏中,Alice和Bob如何通过轮流回答是否知道答案来确定答案范围的逻辑,分为四种情况并提供了代码实现。关键在于理解信息传递和确定性的增加过程。

这个题最早在 LRJ 的黑书上看到过类似的,当时看的一脸懵逼,不过这次就不是那么懵了。

对着题解看了好久,总觉得哪里还不是太理解,觉得有些地方和自己理解的不一样,于是按自己的理解写一份题解。

实际上这个题的内在逻辑,只要我们拆分出来所有的情况,就很简单了。

f[i][j][k]f[i][j][k]f[i][j][k]表示在第 ii 回合(每个人回答一次知道或不知道,算一个回合),第 ii 个人能否确定答案是不是j,k(j≤k)jj,k(j\leq k)jj,k(jk)j 这两个数。

那么,这个东西一共就4种情况(拿 Bob 举例):

  • CASE1CASE1CASE1f[i−2][j][k]f[i-2][j][k]f[i2][j][k],也就是如果上一轮这个人已经知道了,那这回合必然也知道。
  • CASE2CASE2CASE2f[i−1][j+t][k−t](s≤j+t≤k−t)f[i-1][j+t][k-t](s\leq j+t \leq k-t)f[i1][j+t][kt](sj+tkt)中,仅有 f[i−1][j][k]f[i-1][j][k]f[i1][j][k] 这一项是不知道的,那么 Bob 就能根据上一回合, Alice 回答“不知道”,而得知他的答案是 j,kj,k。
  • CASE3CASE3CASE3f[i−2][j+t][k−t](s≤j+t≤k−t)f[i-2][j+t][k-t](s\leq j+t \leq k-t)f[i2][j+t][kt](sj+tkt) 中,仅有 f[i−2][j][k]f[i-2][j][k]f[i2][j][k] 这一项是不知道的,那么 BobBobBob 显然也能在这一回合确定自己的答案是j,kj,kj,k
  • CASE4CASE4CASE4f[i−1][j+t][k−t](s≤j+t≤k−t)f[i-1][j+t][k-t](s\leq j+t \leq k-t)f[i1][j+t][kt](sj+tkt) 中,f[i−1][j][k]f[i-1][j][k]f[i1][j][k] 是确定的,但 (j,k)(j,k)(j,k) 是第 i−1i-1i1 个回合中,AliceAliceAlice 唯一的新增“确定”,那么 BobBobBob 也就能确定自己的答案是 j,kj,kj,k 了。这一种情况最容易忽视,我因为这个卡了好久。具体怎么理解呢,举个例子:BobBobBob 认为答案可能有 (1,5),(2,4),(3,3)(1,5),(2,4),(3,3)(1,5),(2,4),(3,3) 三种情况,而对于 AliceAliceAlice,假设他第一次回答时,只能确定答案是不是 (1,5)(1,5)(1,5),也就是 f[1][1][5]=1f[1][1][5]=1f[1][1][5]=1,第二次回答时,只能确定答案是不是 (1,5)(1,5)(1,5) 以及 (3,3)(3,3)(3,3),也就是 f[3][1][5]=1,f[3][3][3]=1f[3][1][5]=1,f[3][3][3]=1f[3][1][5]=1,f[3][3][3]=1,那么,BobBobBob 就能在第444回合确定答案是不是 3,33,33,3 了,因为如果答案是 3,33,33,3,那么 AliceAliceAlice 会在第一回合说“不知道”,第三回合说“知道”。AliceAliceAlice 视角的推法一样。情况讨论清楚就可以预处理答案了,预处理结束之后就可以按顺序枚举了,只要
f[t+1][j][k]==1 and f[t+2][j][k]==1 and f[i(for all i < t)][j][k]==0
这对 j,kj,kj,k就是一组合法解。
这个做法有一个瑕疵是,对于乘法的分解,有可能分解出来的数很大,数组存不下,但是因为懒,所以就不想改了。

Code:

//如果上一轮,你只在这一种情况不知道,那我就知道了
//如果上上一轮,我只有在这一种情况不知道,那我也知道了 
//如果上一轮,这是你的“首次 ”新增确定,那么我也知道了 
#include <bits/stdc++.h>
using namespace std;
bool f[18][305][305];
string S;
int s,t,flag;
void work1(int turn,int j,int  k) //alice
{
	int num1=0,X1=0,Y1=0; //上一回合有几个不确定
	int num3=0,X3=0,Y3=0; //上上回合有几个不确定
	int num4=0,X4=0,Y4=0; //有几个新增确定
	int tot=j*k;
	for (int x=s;x*x<=tot;x++)
	if (tot%x==0 && tot/x<=300)
	{
		int y=tot/x;
		if (turn==1 || f[turn-1][x][y]==0) {num1++; X1=x; Y1=y;}
		if (turn>=3 && f[turn-2][x][y]==0) {num3++; X3=x; Y3=y;}
		if (f[turn-1][x][y]==1) //确定
		if (turn<=3 || f[turn-3][x][y]==0){num4++; X4=x; Y4=y;}
	}
	if (num1==1 && X1==j && Y1==k) f[turn][j][k]=1;
	if (num3==1 && X3==j && Y3==k) f[turn][j][k]=1;
	if (num4==1 && X4==j && Y4==k) f[turn][j][k]=1;
	return;
}

void work2(int turn,int j,int k) //bob
{
	int num1=0,X1=0,Y1=0;
	int num3=0,X3=0,Y3=0;
	int num4=0,X4=0,Y4=0; 
	for (int x=s;x<=j+k-x;x++)
	{
		int y=j+k-x;
		if (turn==1 || f[turn-1][x][y]==0) {num1++; X1=x; Y1=y;}
		if (turn>=3 && f[turn-2][x][y]==0) {num3++; X3=x; Y3=y;}
		if (f[turn-1][x][y]==1) //确定
		if (turn<=3 || f[turn-3][x][y]==0) {num4++; X4=x; Y4=y;}
	}
	if (num1==1 && X1==j && Y1==k) f[turn][j][k]=1;
	if (num3==1 && X3==j && Y3==k) f[turn][j][k]=1;
	if (num4==1 && X4==j && Y4==k) f[turn][j][k]=1;
	return;
}

int main()
{
	cin>>s>>S>>t;
	if (S=="Alice")  flag=0; else flag=1;
	for (int turn=1;turn<=t+2;turn++)
	{
		for (int j=s;j<=300;j++)
			for (int k=j;k<=300;k++)
			{
				if (turn>=3) f[turn][j][k]|=f[turn-2][j][k];
				if (f[turn][j][k]) continue;
				if (flag==0) work1(turn,j,k);
				else work2(turn,j,k);
			}
		flag=1-flag;	
	}
	int sum=s+s;
	while (true)
	{
		if (sum>=1000) break;
		for (int i=s;i<=sum-i;i++)
		{
			int j=sum-i;
			if (f[t+1][i][j]==0  || f[t+2][i][j]==0) continue;
			int flag=0;
			for (int k=1;k<=t;k++) if (f[k][i][j]==1) flag=1;
			if (flag) continue;
			cout<<i<<" "<<j<<endl;
			return 0;
		}
		sum++;
	}
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值