A. Tenzing and Tsondu(数学,贪心,二叉搜索树)

4 篇文章 0 订阅
1 篇文章 0 订阅
文章讨论了一种两人轮流操作的游戏,其中每个玩家试图最大化自己的获胜机会。初始策略是找到最大值和最小值进行操作,但这种方法效率低。为优化,作者提出使用二叉搜索树来动态存储和查找最大值和最小值,从而减少查询时间。最终,发现游戏的关键在于每次操作减少的值相同,简化了问题,只需计算初始数值总和即可决定胜者。
摘要由CSDN通过智能技术生成

原题地址:A. Tenzing and Tsondu
洛谷上此题地址

题意

  • TsonduTenzing 轮流进行操作,对于每次操作,为了使自己赢的可能性最大,那么他肯定要用自己的最大值对方的最小值进行操作,于是就得到了如下最直接的做法:
#include<cstdio>
int a[100],b[100];
void f(){
	int n,m;
	scanf("%d%d",&n,&m);
	int n1=n,m1=m;//Tsondu和Tenzing手中剩下的数的个数 
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=1;i<=m;++i) scanf("%d",&b[i]);
	int max,maxi,min,mini;
	while(n1||m1){
		max=a[1],maxi=1;
		for(int i=2;i<=n;++i) if(a[i]>max) max=a[i],maxi=i;//求出最大值及其位置 
		min=1e9+1;
		for(int i=1;i<=m;++i) if(b[i]<min&&b[i]) min=b[i],mini=i;//求出最小值及其位置
		a[maxi]=max-min,b[mini]=min-max;//更新这两个数的值 
		if(a[maxi]<=0) a[maxi]=0,n1--;
		if(b[mini]<=0) b[mini]=0,m1--;
		if(!m1||!n1) break;//有人没数了就退出 
		//双方对调再来一次 
		max=b[1],maxi=1;
		for(int i=2;i<=n;++i) if(b[i]>max) max=b[i],maxi=i;
		min=1e9+1;
		for(int i=1 ;i<=m;++i) if(a[i]<min&&a[i]) min=a[i],mini=i;
		b[maxi]=max-min,a[mini]=min-max;
		if(b[maxi]<=0) b[maxi]=0,m1--;
		if(a[mini]<=0) a[mini]=0,n1--;
	}
	if(n1) printf("Tsondu\n");
	else if(m1) printf("Tenzing\n");
	else printf("Draw\n"); 	
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--) f();
	return 0;
}

更优的做法

  • 很显然上述做法是会超时
  • 我们发现时间浪费在了多次查询最大值和最小值上面
  • 首先想到优先队列,但是优先队列要么查询最大值,要么查询最小值,而很难做到一会儿查询最大值,一会儿查询最小值,而用两个优先队列又很难做到相互关联(至少我不知道怎么做 ),所以优先队列不行
  • 但是,我们可以通过优先队列的本质:大根堆 联想到一种类似的数据结构,这种数据结构既能查询最大值又能查询最小值,那就是:二叉搜索树
  • 我们需要两棵二叉搜索树,代码如下:
#include<cstdio>
const int maxn=1e6+5; 
struct node{
	int fa;//父亲 
	int l,r;//左右儿子 
	int v;//权值 
}a[maxn],b[maxn];
int cnt,cnt1,root,root1;
int read(){//快读 
	char c=getchar();
	int x=0,f=0;
	while(c<'0'||c>'9') c=getchar(),f=c=='-'?-1:1;
	while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return f?-x:x;
}
void insert(int rt,int v){//插入 
	if(!root){//如果没有根节点,就让他变成根 
		root=++cnt;
		a[root].v=v;
		return;
	}
	if(v<a[rt].v){
		if(!a[rt].l){
			a[rt].l=++cnt;
			a[cnt].v=v;
			a[cnt].fa=rt;
		}
		else insert(a[rt].l,v);
	}
	else{
		if(!a[rt].r){
			a[rt].r=++cnt;
			a[cnt].v=v;
			a[cnt].fa=rt;
		}
		else insert(a[rt].r,v);
	}
}
int qmax(int rt){//查询最大值 
	if(a[rt].r) return qmax(a[rt].r);
	return a[rt].v;
}
int qmin(int rt){//查询最小值 
	if(a[rt].l) return qmin(a[rt].l);
	return a[rt].v;
}
void shanchu(int rt,int v){//删除 
	if(v>a[rt].v) shanchu(a[rt].r,v);
	if(v<a[rt].v) shanchu(a[rt].l,v);
	if(v==a[rt].v){
		int fa=a[rt].fa;
		if(!a[rt].l&&!a[rt].r){//如果没有儿子就直接删掉 
			if(rt==a[fa].r) a[fa].r=0;
			if(rt==a[fa].l) a[fa].l=0;
			if(rt==root) root=0;//如果是根节点就要让根变成0 
			return;
		}
		if(a[rt].l&&!a[rt].r){//如果只有左儿子,就把左儿子直接接到父节点上 
			if(rt==root){
				root=a[rt].l;
				return;
			}
			if(rt==a[fa].r) a[fa].r=a[rt].l;
			if(rt==a[fa].l) a[fa].l=a[rt].l;
			a[a[rt].l].fa=fa;//更新父子关系 
			return;
		}
		if(a[rt].r&&!a[rt].l){//如果只有右儿子,就把右儿子直接接到父节点上
			if(rt==root){
				root=a[rt].r;
				return;
			}
			if(rt==a[fa].r) a[fa].r=a[rt].r;
			if(rt==a[fa].l) a[fa].l=a[rt].r;
			a[a[rt].r].fa=fa;//更新父子关系 
			return;
		}
		//如果既有左儿子又有右儿子,
		//那么可以用左子树的最大值(或者右子树的最小值)代替它
		//再把子树的最大值(或者右子树的最小值)删掉 
		int ma=qmax(a[rt].l);
		shanchu(a[rt].l,ma);
		a[rt].v=ma;
	}
}
//第二棵二叉搜索树同理 
void insert1(int rt,int v){//插入 
	if(!root1){
		root1=++cnt1;
		b[root1].v=v;
		return;
	}
	if(v<b[rt].v){
		if(!b[rt].l){
			b[rt].l=++cnt1;
			b[cnt1].v=v;
			b[cnt1].fa=rt;
		}
		else insert1(b[rt].l,v);
	}
	else{
		if(!b[rt].r){
			b[rt].r=++cnt1;
			b[cnt1].v=v;
			b[cnt1].fa=rt;
		}
		else insert1(b[rt].r,v);
	}
}
int qmax1(int rt){//查询最大值 
	if(b[rt].r) return qmax1(b[rt].r);
	return b[rt].v;
}
int qmin1(int rt){//查询最小值 
	if(b[rt].l) return qmin1(b[rt].l);
	return b[rt].v;
}
void shanchu1(int rt,int v){//删除 
	if(v>b[rt].v) shanchu1(b[rt].r,v);
	if(v<b[rt].v) shanchu1(b[rt].l,v);
	if(v==b[rt].v){
		int fa=b[rt].fa;
		if(!b[rt].l&&!b[rt].r){
			if(rt==root1) root1=0;
			if(rt==b[fa].r) b[fa].r=0;
			if(rt==b[fa].l) b[fa].l=0;
			return;
		}
		if(b[rt].l&&!b[rt].r){
			if(rt==root1){
				root1=b[rt].l;
				return;
			}
			if(rt==b[fa].r) b[fa].r=b[rt].l;
			if(rt==b[fa].l) b[fa].l=b[rt].l;
			b[b[rt].l].fa=fa;
			return;
		}
		if(b[rt].r&&!b[rt].l){
			if(rt==root1){
				root1=b[rt].r;
				return;
			}
			if(rt==b[fa].r) b[fa].r=b[rt].r;
			if(rt==b[fa].l) b[fa].l=b[rt].r;
			b[b[rt].r].fa=fa;
			return;
		}
		int ma=qmax1(b[rt].l);
		shanchu1(b[rt].l,ma);
		b[rt].v=ma;
	}
} 
void f(){
	int n=read(),m=read(),ma=0,mi=0;
	for(int i=1;i<=n;++i) insert(root,read());//建树 
	for(int i=1;i<=m;++i) insert1(root1,read());
	while(1){
		ma=qmax(root);
		mi=qmin1(root1);
		if(!ma&&!mi){//同时变成了0,平局 
			printf("Draw\n");
			break;
		}
		if(!ma){//Tenzing  win 
			printf("Tenzing\n");
			break;
		}
		if(!mi){//Tsondu  win 
			printf("Tsondu\n");
			break;
		}
		shanchu(root,ma),shanchu1(root1,mi);//删除原先的数 
		if(ma>mi) insert(root,ma-mi);
		if(ma<mi) insert1(root1,mi-ma);//加入新的数 
		//双方对调再来一次 
		ma=qmax1(root1);
		mi=qmin(root);
		if(!ma&&!mi){
			printf("Draw\n");
			break;
		}
		if(!mi){
			printf("Tenzing\n");
			break;
		}
		if(!ma){
			printf("Tsondu\n");
			break;
		}
		shanchu1(root1,ma),shanchu(root,mi);
		if(ma>mi) insert1(root1,ma-mi);
		if(ma<mi) insert(root,mi-ma);
	}
	for(int i=1;i<=cnt;++i) a[i].fa=a[i].l=a[i].r=a[i].v=0;
	for(int i=1;i<=cnt1;++i) b[i].fa=b[i].l=b[i].r=b[i].v=0;
	cnt=cnt1=0,root=root1=0;
}
int main(){
	int t=read();
	while(t--) f();	
	return 0;
}

最优解

  • 很显然这道题还可以用更高级的平衡树来解决,这里就不讲了
  • 我们通过分析可以发现,其实这是一道数学题
  • 我们惊奇的发现:
x=max(x-y,0)=x-min(x,y)
y=max(y-x,0)=y-min(x,y) 
  • 每次操作后两个人手上的数减少的值是相同的!
  • 那么很显然谁手上的数的总和最大,谁就是winer
  • 所以我们得到了终极代码:
#include<cstdio>
int read(){//快读 
	char c=getchar();
	int x=0,f=0;
	while(c<'0'||c>'9') c=getchar(),f=c=='-'?-1:1;
	while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return f?-x:x;
}
void f(){
	int n=read(),m=read();
	long long s=0,s1=0;//50*1e9会爆int !!! 
	for(int i=1;i<=n;++i) s+=read();//求和 
	for(int i=1;i<=m;++i) s1+=read();
	if(s==s1) printf("Draw\n");//比较 
	if(s<s1) printf("Tenzing\n");
	if(s>s1) printf("Tsondu\n");
}
int main(){
	int t=read();
	while(t--) f();	
	return 0;
}

反思

  • 对于这道题,很显然我刚开始是想复杂了
  • 这道题的关键在于分析题意,要是能够发现每次操作后减小的值相同的话,那么这道题就非常好做了
  • 所以做题时要先分析简化题意,记住A题不可能用到复杂的数据结构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Robin_w2321

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值