【题解】牛客练习赛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)); }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530329.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(5)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示