【题解】牛客练习赛86

A.

一句话题意:在纸上写一个数字 n,双方每次选择 n 的一个因数,然后划掉 n 并在纸上写下 n减去这个数字的差 使之成为新的 n,最后写数字 0 的人输。Alice 先手,请请你判断谁会获胜。

Solution:

典型的 SG 函数。打表可以发现若 n 是偶数,则先手必胜;若 n 是奇数,则先手必败。

简单证明:显然 n=1 时必败,若 n 为奇数,则一次操作后必为偶数,而 1 是任何数的因数,所以又会变成奇数,故先手必败;反之,则先手必胜。

B.

简单题意:求一个字符串 s 恰好有 k 种还原方式,即满足 ABC 三个数字视为字符串拼接起来后的结果为 sA+B=C。如 s="123" ,那么可以还原出 1+2=3。不能有前导零,可以为 0n<=100k<=2

Solution:

直接按自然数的顺序枚举即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mx=2e5+5;
int n,k,fac[20],f[20],a[20],cnt;
//贪心好题。 
int main() {
//	freopen("data.in","r",stdin);
//	freopen("own.out","w",stdout);
    fac[0]=1; for(int i=1;i<=18;i++) fac[i]=fac[i-1]*10;
	scanf("%d%d",&k,&n);
	for(ll i=1;;i++) {
        ll tmp=i;cnt=0;
        while(tmp) a[++cnt]=tmp%10,tmp/=10;
        reverse(a+1,a+1+cnt);
        for(int j=1;j<=cnt;j++) f[j]=f[j-1]*10+a[j];
        int ok=0;
        for(int j=1;j<cnt;j++) {
            for(int k=j+1;k<cnt;k++) {
                if((k-j>1&&!a[j+1])||(cnt-k>1&&!a[k+1])) continue;
                ll a=f[j],b=f[k]-f[j]*fac[k-j],c=f[cnt]-f[k]*fac[cnt-k];
                if(a+b==c) ok++;
            }
        }
        if(ok==k) {
            cout<<i<<endl;
            if(!--n) return 0;
        }
    }
} 

C.

一句话题意:有一个货币系统,按贪心策略给取款人发放钞票,即每次都为能取得的面额的最大值。求对于取款金额不超过 b 的方案中,最多能取多少张钞票,输出最大值和相应的取款金额。

Solution:

贪心+递推 好题。

首先由于取款的面额一定,所以选择面额小的钞票越多越好。

引理:对于任意取款金额 c ,若面额最小的钞票取得最多,则取款的张数一定最多。

证明:假设多选一张面额为 1 的钞票,则对于 a_2 剩的钱一定更多,所以 a_2 的张数不会减少,以此类推,直到 a_n 的张数减少,又因为 a_1<a_n ,所以一定更优。证毕。

于是考虑递推,考虑在满足 c<a_i 的前提下以上一次的结果为基础,求到 a_{i-1} 能选到的最大张数。

最后处理答案。二分找到 ps_i<=b<ps_{i+1} ,此时 b<a_{i+1} ,所以不会选择 a_{i+1} ,求出 a_{i} 能选到的最大张数即可。

但是可能存在 b 没有取满的情况,但为了满足引理,此时不能选择更多的 a_{i} ,所以对答案没有影响。

时间复杂度 O(nlogn) 。妙极。

#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
using namespace std;
const int mx=2e5+5;
int n,q;
ll cost,a[mx];
ll dp[mx],ps[mx];
//贪心:尽量选择面额较小的货币 
//yy 一下贪心策略的正确性 
int main() {
//	freopen("data.in","r",stdin);
//	freopen("own.out","w",stdout);
	scanf("%d",&n);
	a[0]=1;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++) {
		dp[i]=dp[i-1]+(a[i]-1-ps[i-1])/a[i-1];
		ps[i]=ps[i-1]+(a[i]-1-ps[i-1])/a[i-1]*a[i-1];
	}
	scanf("%d",&q);
//	printf("nmsl");
	while(q--) {
		scanf("%lld",&cost);
		int it=upper_bound(ps+1,ps+1+n,cost)-ps-1;
//		cout<<it<<" "<<ps[it]<<endl;
		printf("%lld %lld\n",ps[it]+(cost-ps[it])/a[it]*a[it],dp[it]+(cost-ps[it])/a[it]);
	}
} 

D.

题意:给定一个长度为 n 的序列 a ,可以进行 k 次区间翻转操作,求最终序列最多有多少对相邻两项满足 a_i!=a_{i+1}

Solution:

这道题不是很严谨,有点偏思维。我的做法很玄学,但是过掉了。

首先确定答案的上界,假设出现次数最多的数字出现了 x 次,若 x>ceil(n/2) ,则最多有 2(n-x) 对数,否则最多 n-1 对数。

然后这道题可以转化为对于一些数,每次选两个减去 1 ,最多操作多少次。这样每次操作贡献为 2 。可以每次选最大数和次大数进行操作。最后如果只剩下一个数,就和其他已经匹配好的数操作,每次贡献为 1
在这里插入图片描述

下面我们来证明一定存在已经匹配的数与之匹配。

首先一定存在已经匹配的数,否则说明只有一种相同的数字,答案上限为 0

其次,假如每一对都存在 a ,而此时序列中只存在连续的 a ,可以发现 a 会交错出现,那么假若首位不是 a ,还可以各翻转一次;否则 cnt(a)>ceil(n/2) ,已经达到了答案上界,所以剩下的操作没有贡献。

在这里插入图片描述
综上,时间复杂度 O(nlogn)

#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
using namespace std;
const int mx=5e5+5;
int n,m,a[mx],d[mx],maxappear,maxpair,cnt,res,res2;
multiset<int,greater<int> > s;
//注意一下终止状态 
int main() {
//	freopen("data.in","r",stdin);
//	freopen("own.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		d[a[i]]++;
	}
	for(int i=1;i<=n;i++) maxappear=max(maxappear,d[i]);
	if(maxappear>(n+1)/2) {
		maxpair=2*(n-maxappear);
	}
	else {
		maxpair=n-1;
	}
	memset(d,0,sizeof(d));
	for(int i=2;i<=n;i++) {
		if(a[i]==a[i-1]) d[a[i]]++;
		else res++;
	}
	for(int i=1;i<=n;i++) {
		if(d[i]) s.insert(d[i]);
	}
	while(m&&s.size()>1) {
		int x=*s.begin();
		s.erase(s.begin());
		int y=*s.begin();
		s.erase(s.begin());
	    res+=2,m--,x--,y--;
	    if(x) s.insert(x);
		if(y) s.insert(y); 
	}
	int cnt=0;
	for(auto x:s) {
		cnt+=x;
	}
	res+=min(m,cnt);
	printf("%d",min(res,maxpair));
} 
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值