2021-牛客多校第一场(ABDFG)含暴力sg

A-Alice and Bob

博弈,要么败要么胜.在我看来,博弈就是一个状态转移问题,由必胜态必败态两者之间不断转换.常规博弈,比如bash博弈(两个人取m个物品,最多取n个,先取完胜利)这类,就是找到了必胜必败的规律来直接找到问题答案,一般要么记规律,要么暴力打表找规律......当然,像nim游戏这类求异或和再判断的博弈,也可以将问题化为二进制数来寻找规律.你也许会想:博弈就这?但是,这些都是特殊情况,那么在没有特殊情况的一般博弈里面,我们该如何求呢?为了解决这些,我们引入了sg函数.

必胜点,必败点

在引入sg函数之前,先引入必胜点(N点)和必败点(P点)的概念:

顾名思义,必败点(P)就是前一个选手必定取胜的点,必胜点(N)就是下一个选手将取胜的点,这个点是一个状态,而状态转移的规则:从任何必胜点(N)推,必可以转移到至少一个必败点(P),从必败点(P)转移,只能进入必胜点(N).游戏由必败点终结.

mex

另外需要介绍的一个就是mex(s),mex是最小非负数的意思,比如mex(1,3,4)=0,mex=(0,1)=2.mex里面的s是一个集合,他的意思是找出不包含在集合内的最小非负数.而sg函数中,保存的就是mex(该点的前驱节点的所有sg值),这些值都是由已知的终结点--必败点来进行推,暴力sg就是推出所有可能的结果,从而得到想要的答案.

下面,上题!


链接:https://ac.nowcoder.com/acm/contest/11166/A
来源:牛客网
 

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

Alice and Bob like playing games. There are two piles of stones with numbers n{n}n and m{m}m. Alice and Bob take turns to operate, each operation can take away k(k>0){k}(k>0)k(k>0) stones from one pile and take away s×k(s≥0)s \times k(s \geq 0)s×k(s≥0) stones from another pile. Alice plays first. The person who cannot perform the operation loses the game. 

Please determine who will win the game if both Alice and Bob play the game optimally.

输入描述:

The first line contains an integer T(1≤T≤104)T(1 \le T \le 10^4)T(1≤T≤104) denotes the total number of test cases.
Each test case contains two integers n,m(1≤n,m≤5×103)n,m(1 \le n,m \leq 5 \times 10^3)n,m(1≤n,m≤5×103) in a line, indicating the number of two piles of stones.

输出描述:

For each test case, print "Alice" if Alice will win the game, otherwise print "Bob".

示例1

输入

5 2 3 3 5 5 7 7 5 7 7

5
2 3
3 5
5 7
7 5
7 7

输出

Bob Alice Bob Bob Alice

Bob
Alice
Bob
Bob
Alice

题目大意是alice和bob玩游戏,有两堆石头,他们轮流取,不能继续操作的就输了,alice先取.规则是每次一堆取k个(k>0),另一堆取k*s个(s>=0).要输入一个n控制几组样例.输出就输出胜者名字.

这题是我打牛客多校遇到的,当时稍微学了一点点博弈,想挑战来着,结果到最后还是没过,sg没学明白(现在也没学明白).

我一开始就像,尝试纯暴力sg,把所有情况都列举了在算出来,按照规则,只从必败点推,可以剪枝优化一下,于是就有了以下代码:

#include<iostream>
using namespace std;
bool sg[5007][5007];
void init(){
	int i,j,u,k,q,p;
	sg[0][0]=0;
	for(i=0;i<=5000;i++)
	{
		for(j=0;j<=5000;j++)
		{
			if(sg[i][j]!=0)continue;
			for(u=1;u+i<=5000;u++)
			{
				for(k=0;k*u+j<=5000;k++)
				{
					sg[u+i][k*u+j]=true;		
				}
			}
			for(u=1;u+j<=5000;u++)
			{
				for(k=0;k*u+i<=5000;k++)
				{
					sg[k*u+i][u+j]=true;		
				}
			}
		}
	}
	return;
}
int main()
{
	int t,x,y;
	init();
	cin>>t;
	while(t--)
	{
		cin>>x>>y;
		if(sg[x][y]==0)cout<<"Bob"<<endl;
		else cout<<"Alice"<<endl;
	}
	return 0;	
}

代码很好理解,就是遍历所有点,是必败点的话,就从这里出发,按照题意,一边取k张牌,另一边取k*s张牌,这些点就都是必胜点,如果遍历访问到必胜点就直接continue,因为从必胜点可以推出必胜必败两种情况,但是只要遍历所有必败点,并且按规则找必胜点,一定就可以找完所有的点的sg值.在按题意多次访问,就过了.(这里的必胜必败是相对于Alice来说的,因为一开始(0,0)状态是相对于Alice的必败点,只有必败点才能推出必胜点).

其实这个解法并不是最好的写法,代码中sg数组我本来开的是int类型,超时了,后来改为bool类型,1000ms用了800ms将近900ms,有点卡时间过的意思了.

之前我还有另一段代码,由于考虑了从必胜点转化,以及时间复杂度过高的问题,总之就是写了百行,没有过,然后去找学长请教,最终把它优化了,代码如下:

#include<iostream>
using namespace std;
int sg[5007][5007];
void init(){
    for(int i=0;i<=5000;i++){
        for(int j=i;j<=5000;j++){
            if(sg[i][j]==0){
                for(int h=1;i+h<=5000;h++){
                    for(int k=0;j+h*k<=5000;k++){
                        int a=h+i;
                        int b=j+h*k;
                        if(a>b)swap(a,b);
                        sg[a][b]=1;
                    }
                }
                for(int h=1;j+h<=5000;h++){
                    for(int k=0;i+h*k<=5000;k++){
                        int b=h+j;
                        int a=i+h*k;
                        if(a>b)swap(a,b);
                        sg[a][b]=1;
                    }
                }
            }
        }
    }
}

int main()
{
	int tt,t,x,y;
	init();
	cin>>t;
	while(t--){
		cin>>x>>y;
		if(x>y){
			tt=x;
			x=y;
			y=tt;
		}
		if(sg[x][y]==0)cout<<"Bob"<<endl;
		else cout<<"Alice"<<endl;
	}
	return 0;	
}

该段暴力sg时,遍历就不像上一段代码,循环是j必定大于i,这样就导致main函数进行sg值访问时,就要按照左小右大的顺序才能进行访问.其实两段代码性质上都是暴力sg,只是下面的这段进行了剪枝.就可以不用bool类型,虽然使用了时间肯定会进一步缩短.

总结一下,这个题的思路就是从终结点(0,0)出发,寻找所有必败点,找到一个必败点就去逆推出所有必胜点,将所有的情况列举完,这样不论问你哪种状态的胜负情况,都可以直接求出来.


B-Ball Dropping

简单的几何题,只要在途中找相似三角形,分情况讨论就可以求出结果.

 很明显,找到一组三个相似三角形,三角形ABD,三角形CED,三角形CFO,运用边边相比,比值相等直接计算.

#include<iostream>
#include<cmath>
using namespace std;
int main(){
	double ans,a,b,r,h,k,M;
	cin>>r>>a>>b>>h;
	if(r>(b/2)){
		cout<<"Stuck"<<endl;
		k=(a/2)-(b/2);
		M=sqrt(pow(h,2)+pow(k,2));
		ans=(r*M-(b/2)*h)/k;
		printf("%.10lf",ans);
	}
	else{
		cout<<"Drop";
	}
	return 0; 
} 

D-Determine the Photo Position

大意是在n*n的格子中匹配一段1*m的字符串,求出方案数,有些地方不能放字符串了.直接暴力模拟一遍就可以了.

#include<iostream>
using namespace std;
char map[2007][2007],t[2007];
int main(){
	int m,n,sum=0;
	cin>>m>>n;
	for(int i=0;i<m;i++){
		for(int j=0;j<m;j++){
			cin>>map[i][j];
		}
		getchar();
	}
	for(int i=0;i<n;i++){
		cin>>t[i];
	}
	int tt;
	for(int i=0;i<m;i++){
		tt=0;
		for(int j=0;j<m;j++){
			if(map[i][j]=='0'){
				tt++;
			}
			if(map[i][j]=='1'||j==m-1){
			
				if(tt>=n){
					sum+=(tt-n+1);	
				}
				tt=0;
			}
		}
	}
	cout<<sum;
}

F-Find 3-friendly Integers

先定义了3的友好型数,该数本身以及某几个连续数位mod3等于0.例如127中12%3==0,105中105%3==0,0%3==0.看到该题,应该会想到数位dp,但是本大意蒟蒻暂时还没有进行数位dp的学习,本来以为可以放弃了,没想到有一个隐藏的规律:大于100的全是3-friendly Integers.那么最直白的做法就是打表,把100以内的打表记录,之后全部都是3-friendly Integers.

这个题告诉我吗当你什么办法都想不出来的时候,打表是可以给你惊喜的.

#include<bits/stdc++.h>
using namespace std;
long long a[24]={1,2,4,5,7,8,11,14,17,22,25,28,41,44,47,52,55,58,71,74,77,82,85,88};//24
int main()
{
	long long T,i,j,F;
	cin>>T;
	while(T--)
	{
		long long x=0,y=0,l,r;
		cin>>l>>r;
		for(i=0;i<24;i++)
			if(a[i]<l)
				x++;
		for(i=0;i<24;i++)
			if(a[i]<=r)
				y++;
        y-=x;
        cout<<r-l+1-y<<endl;
	}
	return 0;
}

G-Game of Swapping Numbers

题意是给你两个长度为k的数组A,B,对与A组数组进行K次操作,将A中两个数字进行调换,让对应位置的A[i]-B[i]的绝对值最大,输出最大的绝对值.

首先可以把每个相应位置的A[i]和B[i]分类,求绝对值的话,肯定是大减去小,所以我们可以把两者较大的分为一类,较小的分为一类,此时如果我们将A中同类进行交换,对结果不会有影响,因为它们总是减数或者被减数,就算是掉换位置,该加还是加,该减还是减,对于整体无影响,例如A[]={1,2,3},B[]={3,1,1},那么将2,3交换,由于它们都被分类为大的,也就是被减数,结果不会改变.但如果两者种类不同,由被减数变成减数或者由减数变成被减数,那么值就会改变,例如把A中1,2交换,结果就从5变成3.这里还有个k次限制,我们可以理解为最大为k次,因为拥有同类交换值不变的情况,但是此时存在一个特殊样例:当n==2时,你会发现它进行交换结果是取决于它的交换次数的,所以拿出来特判.

交换方法找到了,令结果最大,那我们每次都拿场上大类里最小和最小类里最大来进行匹配,把每次改变对结果的影响加上即可.

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
bool cmp(int a,int b)
{
	return a>b;
}
long long a[500007],b[500007],minn[500007],maxx[500007];
int main()
{
	long long n,k;
	long long ans=0;
	cin>>n>>k;
	for(long long i=0;i<n;i++)
	{
		cin>>a[i];
	}
	for(long long i=0;i<n;i++)
	{
		cin>>b[i];
	}
	for(long long i=0;i<n;i++)
	{
		maxx[i]=max(a[i],b[i]);
		minn[i]=min(a[i],b[i]);
	}
	if(n==2)
	{
		if(k%2==1)
		{
			cout<<abs(a[0]-b[1])+abs(b[0]-a[1]);
		}
		else
		{
			cout<<abs(a[0]-b[0])+abs(b[1]-a[1]);
		}
		return 0;
	}
	sort(maxx,maxx+n);
	sort(minn,minn+n,cmp);
	for(long long i=0;i<n;i++)
	{
		ans+=abs(a[i]-b[i]);
	}
	for(long long i=0,j=0;i<n&&j<k;i++,j++)
	{
		if(maxx[i]<minn[i])
			ans+=2*(minn[i]-maxx[i]);
		else break;
	}
	cout<<ans;
	return 0;
}

蒟蒻目前就补了这几个题,因为较为简单,别的题以后会回来补题的.

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值