牛客练习赛121(A,B,C,D,E)

文章分析了几道编程竞赛题目,涉及数组操作、博弈论思维、动态规划方法,如判断必胜必败状态、利用贪心策略凑数、以及优化使用折扣券和立减券的策略。
摘要由CSDN通过智能技术生成

出题人题解
比赛链接


A 小念吹气球

思路:

拿个数组标记一下这个题有没有通过的人,如果是一血,就多给个气球。

code:

#include <iostream>
#include <cstdio>
using namespace std;

int n,ans;
bool vis[30];
char ch;

int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>ch;
		if(!vis[ch-'A']){
			vis[ch-'A']=true;
			ans++;
		}
		ans++;
	}
	cout<<ans;
	return 0;
}

B You Brought Me A Gentle Breeze on the Field

思路:

感觉像是博弈论,和取火柴这题很像,不过这个不是。因为两人每次操作都是最优的,这意味着双方都不会失误导致局面出现变数,对一个状态显然只有必胜和必败两种状态。

必胜状态后面必定有一个必败状态(也就是面临必胜状态的人一定可以将必败状态留给对手),而必败状态后面都是必胜状态(也就是面临必败状态的人一定会把必胜状态流给对方)。

这样的话,如果取糖果轮次够用,那么拥有额外回合的人一定可以反败为胜,因为必败状态后面肯定有必胜状态。

那么问题就转化成了取糖果能不能一局定胜负,否则拥有额外回合的人一定获胜。有这样几种情况:

  1. n = 1 n=1 n=1,先手必败
  2. n ∈ [ 2 , m + 1 ] n\in[2,m+1] n[2,m+1],先手必胜
  3. n > m + 1 n>m+1 n>m+1,一轮不能定胜负,有额外回合的人一定获胜

如果没有额外回合规则,那么这就和取火柴的题是一样的了,或者说是一种 巴什博弈参考2

code:

#include <iostream>
#include <cstdio>
using namespace std;

int T,n,m,p;

int main(){
	cin>>T;
	while(T--){
		cin>>n>>m>>p;
		if(n==1)puts("YangQiShaoNian");
		else if(n<=m+1)puts("XiaoNian");
		else puts((p)?"YangQiShaoNian":"XiaoNian");
	}
	return 0;
}

C 氧气少年的水滴 2

思路:

由于一个水珠爆裂后只有可能对左右两边产生影响,被影响的水珠才有可能爆裂。所以我们直接看爆裂的水珠两边的水珠就可以了,用两个指针模拟水珠爆裂的过程。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=2e5+5;

int T,a[maxn],n,p;

int main(){
	cin>>T;
	while(T--){
		cin>>n>>p;
		for(int i=1;i<=n;i++)
			cin>>a[i];
		if(a[p]+1<10){
			puts("0 0");
			continue;
		}
		int l=p-1,r=p+1,lc=1,rc=1;
		bool f;
		while(true){
			f=false;
			while(l>=1 && a[l]+lc>=10){
				lc-=10-a[l];
				l--;
				lc++;
				rc++;
				f=true;
			}
			while(r<=n && a[r]+rc>=10){
				rc-=10-a[r];
				r++;
				lc++;
				rc++;
				f=true;
			}
			if(!f)break;
		}
		printf("%d %d\n",(l<1)?lc:0,(r>n)?rc:0);
	}
	return 0;
}

D 氧气少年的 LCM

思路:

构造题,不难想到 x y xy xy,要凑的 l c m ( x , y ) lcm(x,y) lcm(x,y) 以及使用两种操作造出来的所有数都一定含有因数 g c d ( x , y ) gcd(x,y) gcd(x,y),因此不妨一开始先不看 g c d ( x , y ) gcd(x,y) gcd(x,y),把 x y xy xy 中的 g c d ( x , y ) gcd(x,y) gcd(x,y) 除掉,这样 x y xy xy 一定是互质的, l c m = x ∗ y , g c d = 1 lcm=x*y,gcd=1 lcm=xy,gcd=1,不妨再给 x y xy xy 排下序,使得 x < y x<y x<y

如果我们取一次操作1,就能得到一个1,再取一次就能得到两个,然后对两个1进行操作2,就能得到一个2,再进行一次得到两个2,类推,能得到两个4,两个8…于是我们就可以使用二进制来凑出答案了。

答案要求操作不超过200次,而二进制最多跑到 2 60 2^{60} 260 不到,也就是最多使用120次准备操作,而凑数再用60次,总的操作次数一定不会超过200。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=205;

int T;
ll x,y;
ll gcd(ll a,ll b){
	while(b)b^=a^=b^=a%=b;
	return a;
}

struct opt{
	int op;
	ll n1,n2;
}opt[maxn*maxn];
int num;

ll lowbit(ll x){return x&-x;}

int main(){
	cin>>T;
	while(T--){
		cin>>x>>y;
		if(x>y)swap(x,y);
		ll gd=gcd(x,y),lm=x*y/gd/gd;
		x/=gd;
		y/=gd;
		if(lm==y){
			puts("0");
			continue;
		}
		
		num=0;
		opt[++num].op=1;
		opt[num].n1=x;
		opt[num].n2=y;
		opt[++num].op=1;
		opt[num].n1=x;
		opt[num].n2=y;
		for(int i=1;i<=59 && (1ll<<i)<=lm;i++){
			opt[++num].op=2;
			opt[num].n1=1ll<<(i-1);
			opt[num].n2=1ll<<(i-1);
			opt[++num].op=2;
			opt[num].n1=1ll<<(i-1);
			opt[num].n2=1ll<<(i-1);
		}
		
		ll lst=lowbit(lm);
		lm-=lowbit(lm);
		while(lm){
			opt[++num].op=2;
			opt[num].n1=lst;
			opt[num].n2=lowbit(lm);
			lst+=lowbit(lm);
			lm-=lowbit(lm);
//			cout<<lm<<endl;
		}
		
		printf("%d\n",num);
		for(int i=1;i<=num;i++)
			printf("%d %lld %lld\n",opt[i].op,opt[i].n1*gd,opt[i].n2*gd);
	}
	return 0;
}

E 氧气少年逛超市 3

思路:

这题出题人题解给的讲解很详细严谨。这题是个贪心+dp。把出题人题解的图粘过来一下:
在这里插入图片描述

不管使用哪个券,一定是用在大价钱的东西上最好,所以前 m i n ( a + b , n ) min(a+b,n) min(a+b,n) 个商品一定用券。对折扣券,如果一个商品要用券,一定是先使用小的最好,这样折扣的钱会更多。对立减券,一定是先用大的最好,这样减掉的钱最多。因此规定了这几个规则后,就可以使用dp进行递推了。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=5005;
const int inf=1e9;

int T,n,p[maxn];
int a,x[maxn];
int b,y[maxn];

double dp[maxn][maxn];//前i个物品 用j个折扣券 i-j个立减券 


int main(){
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++)cin>>p[i];
		cin>>a;
		for(int i=1;i<=a;i++)cin>>x[i];
		cin>>b;
		for(int i=1;i<=b;i++)cin>>y[i];
		sort(p+1,p+n+1,greater<int>());
		sort(x+1,x+a+1);
		sort(y+1,y+b+1,greater<int>());
		
		for(int i=1;i<=min(a+b,n);i++){
			for(int j=0;j<=min(i,a);j++){
				dp[i][j]=inf;
				//尝试使用折扣券 
				if(j>=1 && j<=a)dp[i][j]=min(dp[i][j],dp[i-1][j-1]+x[j]*p[i]/100.0);
				//尝试使用立减券 
				if(i-j>=1 && i-j<=b)dp[i][j]=min(dp[i][j],dp[i-1][j]+max(0,p[i]-y[i-j]));
//				printf("dp(%d,%d)=%lf\n",i,j,dp[i][j]);
			}
		}
		double ans=inf;
		for(int i=0;i<=a;i++)
			ans=min(ans,dp[min(a+b,n)][i]);
		for(int i=a+b+1;i<=n;i++)
			ans+=p[i];
		printf("%lf\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值