牛客周赛 Round 38 题解

文章讲述了编程中涉及的几个问题,包括小红的整数操作(自增和抛弃后缀),字符串构建,平滑值插值,等比数列分析,回文子序列查询,以及区间删除问题的解决方案,主要使用了C++编程语言和一些常见的数据结构技巧。
摘要由CSDN通过智能技术生成

A. 小红的正整数自增

在这里插入图片描述

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N=1e5+10;
int n,m,a[N];
void solve()
{
	cin>>n;
    if(n%10==0) cout<<0<<endl;//末尾为0,直接输出
    else cout<<10-n%10<<endl;//10-末尾
}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);
	int T=1;
//	cin>>T;
	while(T--)
	{
		solve();
	}
	return 0;
}

B. 小红的抛弃后缀

在这里插入图片描述
由性质(a+b)%p=(a%p+b%p)%p
每次加上一位,然后取模即为结果

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N=1e5+10;
int n,m,a[N];
string s;
void solve()
{
	cin>>s;
	n=s.size();
    int res=0,ans=0;
    for(int i=0;i<n;i++)
    {
        m=s[i]-'0';//字符->整数
        res=(res+m)%9;//每次取模都会让结果变成1位数,无需乘10
        if(res==0) ans++;//为0,答案加1
    }
    cout<<ans;
}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);
	int T=1;
//	cin>>T;
	while(T--)
	{
		solve();
	}
	return 0;
}

C. 小红的字符串构造

在这里插入图片描述
可以观察到:
abab,存在一个回文串
abcabcabc不存在回文串
所以只需要三个字母依次循环输出,回文数为0

再观察:
aa,一个回文串
abba,两个回文串
abccba,三个回文串
abcaacba,四个回文串
所以要想输出k个回文串,2*k个字母足以,题目范围(k<=n/2),满足
所以每次正着输出k个,再倒着输出k个,然后再用另外三个字母补到n就行了

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N=1e5+10;
int n,k,a[N];
char c[3]={'a','b','c'},d[3]={'x','y','z'};
void solve()
{
    cin>>n>>k;
    for(int i=0;i<k;i++) cout<<c[i%3];//正
    for(int i=k-1;i>=0;i--) cout<<c[i%3];//倒
    for(int i=k*2;i<n;i++) cout<<d[i%3];//补
}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);
	int T=1;
//	cin>>T;
	while(T--)
	{
		solve();
	}
	return 0;
}

D. 小红的平滑值插值

在这里插入图片描述
每次找相邻两个数的差就行了
有一点,k=2时,差为3时,1个就行,即3/2
但是,差为4时,也是1个就行了,所以这时候先减1就行,(4-1)/2
最后一种,就是最大差值小于k,我们只需要在a,b间加上一个数就行,取min(a,b)+k

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N=1e5+10;
int n,k,a[N],b[N];
void solve()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    int mx=0;
    for(int i=1;i<n;i++) b[i]=abs(a[i+1]-a[i]),mx=max(mx,b[i]);
    int ans=0;		   //记录每两位差值,最大差值,mx<k,就可以直接输出1了
    for(int i=1;i<n;i++)
    {
        if(b[i]>k)
        {
            if(b[i]%k==0) b[i]--;//整除,就让它不整除
            ans+=b[i]/k;//每次加上
        }
    }
    if(mx<k) ans++;//+1
    cout<<ans;
}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);
	int T=1;
//	cin>>T;
	while(T--)
	{
		solve();
	}
	return 0;
}

E. 小苯的等比数列

在这里插入图片描述
注意他们之间可能有多组重复,如 1 2 1 1 2这种
所以需要提前记录个数,取最大值
暴力即可,经过计算,公比>2,累乘>2e5,需要乘的没几个
从2~n,总计4e5左右(记不清了),所以再剪枝就行了
如果当前个数为3,公比为2,当前数为x
若x*23>mx(数组中的最大值),那么从x开始,公比为2,就没必要再算了,再算也不可能超过3
这样将会少算非常非常多次,时间大概率是够了

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N=2e5+10;
int n,m,a[N],mp[N];
void solve()
{
    int ans=1;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++,ans=max(ans,mp[a[i]]);
    sort(a+1,a+n+1);//排序		  //提前记录个数,取最大值,此时公比为1
    for(int i=2;i<=a[n];i++)//从2开始,枚举公比,不可能超过a[n]
    {
        for(int j=1;j<=n;j++)//枚举n个元素
        {
            if(pow(i,ans)*a[j]>a[n]) break;//最重要的一步,剪枝,如果害怕爆范围可以log求
            int res=a[j],cnt=1;//对a[j]进行倍增
            while(res*i<=a[n]&&mp[res*i])//在范围内,且存在
            {
                res*=i;
                cnt++;
            }
            ans=max(ans,cnt);//取max
        }
    }
    cout<<ans;//输出0^0
}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);
	int T=1;
//	cin>>T;
	while(T--)
	{
		solve();
	}
	return 0;
}

F. 小苯的回文询问

在这里插入图片描述
回文长度>2,即存在(a,b,a)这种情况即可
因为是子序列,随便选三个,有两个相同,两个之间有一个就行
我们可以先排个序,这样相邻两个就是相同的(这里有点不好说,懂我意思就行),
再存一下下标,判断相同的两个下标,是否相差>2即可

按顺序排序,第一个找到的就是最小的
直接令 m [ i ] = id,即可

至于不存在相同,或者相同但跨度过大
如 3 1 2 1 1 4 1 2
对4,直接取>n的数就行
对3,取它后面离他最近的一对就行
对第一个2,本来跨度为6,但是,有两个1,在两个2中间,且跨度为4,所以,
取(下标,从1开始)[3,7],而不是[3,8]

对于询问的区间[l,r],我们只需要判断 m [ l ] 是否小于r即可

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N=1e5+10;
#define x first //提前定义x为first,y为second
#define y second
int n,q,a[N],b[N];//b[i]存i对应的最小右边界
void solve()
{
    vector<pair<int,int>>v;//pair,相当于结构体,排序时,默认先按第一个排,再按第二个元素排
	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i],v.push_back({a[i],i});
    sort(v.begin(),v.end());		//先按元素大小,再按下标
    for(int i=0;i<n;i++)
    {
        for(int j=1;j<=2;j++)//排好序的元素,相邻两个的下标的差可能为1,也可能>1
        {					 //但是不相邻的两个一定>1,所以这里比较[i,i+j]
            if(i+j<n&&v[i].x==v[i+j].x&&v[i+j].y-v[i].y>=2)
            {//在范围内,两个元素相等,下标差>=2
                b[v[i].y]=v[i+j].y;//第一个就是最小的,直接break
                break;
            }
        }
    }
    int mn=n+1;//取一个大于n的值
    for(int i=n;i>=1;i--)//☆倒着遍历
    {
        if(b[i])//b[i]有对应值,就先每次取min,以便没有对应值的元素取值,
            mn=min(mn,b[i]);			//同时也是更新每个元素能对应的最小值
        b[i]=mn;//更新					//即上面举例中两个2的更新
    }
    while(q--)
    {
        int l,r;
        cin>>l>>r;
        if(b[l]<=r)//l对应的最小右边界<=r,YES,否则,NO
        {
            cout<<"YES\n";
        }
        else cout<<"NO\n";
    }
}
signed main()
{
	ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);
	int T=1;
//	cin>>T;
	while(T--)
	{
		solve();
	}
	return 0;
}

G. 小红的区间删除

在这里插入图片描述
树状数组+双指针
我们先算出整个区间[1,n]的逆序对数量
因为数据范围是1e6,所以每次加入 a [ i ],然后加上 a [ i ] 前面有几个比它大的数的个数就行
树状数组每次询问都是前x个的和,所以算逆序对总数 (res),每次加从最后面减去当前即可
(1,n)-(1,x)=(x,n),注意这里n并不是个数,而是最大值

当然,如果数据范围很大,比如1e9,就需要离散化了

每次去掉一个数时,比如 5 4 3 2 1,去掉 [2,2] ,当前逆序对总数即为
res = res - [1,2) 中比 4 大的个数 - (2,5] 中比 4 小的个数,
去掉 [3,3],res = res - [1,3)中比 3 大的个数 - (3,5] 中比 3 小的个数

假使去掉一个区间 [2,3],比如 5 4 3 2 1,此时 res = 10
先去掉 [2,2],res = 10 - (1 + 3)= 6,再去掉 [3,3],注意,此时已经去掉第二个元素,
所以此时,res = 6 -(1 + 2)= 3

设区间 [ l,r ]
我们每次增加右端点 r,每次 r 都会有一个对应的 l,使得 res - [ l,r ] 的逆序对个数最接近 k,
每次加上 r - l + 1,即加入区间 [ l,r ],[ l + 1,r ],… ,[ r,r ],总个数为 r - l +1,
可以想到,对于每一个 r,都加入了一定的满足条件的区间,这样总和起来的区间将是不重不漏的

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N=1e6+10;
int n,k,a[N],L[N],R[N];//两个树状数组,L存这个数左边比它大的个数,R存右边比它小的个数
int lowbit(int x)
{
    return x & -x;
}
void add(int x,int c,int tr[])
{
    for(int i=x;i<N;i+=lowbit(i)) tr[i]+=c;
}
int sum(int x,int tr[])
{
    int res=0;
    for(int i=x;i>=1;i-=lowbit(i)) res+=tr[i];
    return res;
}
void solve()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    int res=0,cnt=0,mx=N-1;//a中元素最大值不超过mx
    for(int i=1;i<=n;i++)
    {
        add(a[i],1,R);//a[i]的个数加1
        cnt+=sum(mx,R)-sum(a[i],R);//每次加入 (a[i],mx]的总和,即大于a[i]的个数
    }
    if(cnt>=k) res++;//如果不删除满足,答案加1
    int l=1,r=1;
    while(r<=n)//r从1~n
    {
        add(a[r],-1,R);//每次删除第r个数,此时-1即删除
        cnt-=sum(mx,L)-sum(a[r],L)+sum(a[r]-1,R);//减去左边比a[r]大的,右边的a[r]小的
        while(cnt<k&&l<=r)//cnt<k,该走左指针了
        {
            add(a[l],1,L);//加入第l个数
            cnt+=sum(mx,L)-sum(a[l],L)+sum(a[l]-1,R);//加上第l个数所能带来的增益
            l++;									 //左边大于a[l]的个数,右边小于a[l]的个数
        }
        res+=r-l+1;//加入区间长度,即个数
        r++;
    }
    cout<<res;//输出
}
signed main()
{
    ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);
    int T=1;
//	cin>>T;
    while(T--)
    {
        solve();
    }
    return 0;
}
  • 24
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值