第十三届蓝桥杯Java B 组国赛 C 题——左移右移(AC)

Posted 执 梗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十三届蓝桥杯Java B 组国赛 C 题——左移右移(AC)相关的知识,希望对你有一定的参考价值。

目录

1.左移右移

1.题目描述

小蓝有一个长度为 N N N 的数组, 初始时从左到右依次是 1 , 2 , 3 , … N 1,2,3, \\ldots N 1,2,3,N

之后小蓝对这个数组进行了 M M M 次操作, 每次操作可能是以下 2 种之一:

  1. 左移 x x x, 即把 x x x 移动到最左边。

  2. 右移 x x x, 即把 x x x 移动到最右边。

请你回答经过 M M M 次操作之后, 数组从左到右每个数是多少?

2.输入格式

第一行包含 2 个整数, N N N M M M 。以下 M M M 行每行一个操作, 其中 “ L x Lx Lx "表示左移 x x x ," R x Rx Rx "表示右移 x x x

3.输出格式

输出 N N N 个数, 代表操作后的数组。

4.样例输入

5 3
L 3
L 2
R 1

5.样例输出

2 3 4 5 1

6.数据范围

1 ≤ N , M ≤ 200000 , 1 ≤ x ≤ N . 1≤N,M≤200000,1≤x≤N. 1N,M200000,1xN.

6.原题链接

左移右移

2.解题思路

  题目的含义非常简单,如果按照朴素的方式遍历寻找 x x x,然后直接进行插入操作,在 n n n的级别在 2 e 5 2e5 2e5的范围这时间复杂度显然是不可接受的。想要解决此题我们需要思考两个点:

  1. 如何高效地进行插入和删除操作
  2. 如何快速地找到某个点所在的位置

  对于第一点,我们应该快速地想到链表这个数据结构,由于题目需要在左端点和右端点都进行插入操作,所以我们应该联想到 双链表 。它可以在 O ( 1 ) O(1) O(1)的时间范围内对元素进行插入和删除,这显然是我们需要的数据结构。
  当然,双链表并不支持高效地查找,所以我们如何快速找到 x x x 的位置呢?这时候我们应该联想到 哈希表,因为我们需要手动实现双链表,所以每个链表结点都对应一个值,同时它也是一个对象,我们可以使用哈希表,以值为 k e y key key,以这个链表结点对象为 v a l u e value value。这样我们就可以快速获得这个结点,然后再进行常规的双链表插入删除操作。

考虑一个更简单的做法,由于每次都将某个数要么变为最大,要么变为最小,那么我们可以记录每个数的权值大小。假设此时最小的数权值为 l l l ,最大的数权值为 r r r ,若要将 x x x 挪到最左边,将其权值赋值为 l − 1 l-1 l1 ,若要将其移动最右边则将其赋值为 r + 1 r+1 r+1,同时更新 l , r l,r l,r。每个数最开始的权值等于其自身,当操作完毕后,按照权值排序得到的序列即是答案。

3.Ac_code

Java

import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.*;

public class Main 
    static Map<Integer,Node> map=new HashMap<>();
    static PrintWriter out=new PrintWriter(new OutputStreamWriter(System.out));
    public static void main(String[] args) 
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        int m=sc.nextInt();
        //双链表的头结点和尾结点
        Node head=new Node(-1,null,null);
        Node last=new Node(-1,null,null);
        Node pre=head;
        //构建双链表
        for (int i = 1; i <=n; i++) 
            pre.next=new Node(i,pre,null);
            pre=pre.next;
            map.put(i,pre);
        
        last.pre=pre;
        pre.next=last;
        for (int i = 0; i < m; i++) 
            char c=sc.next().charAt(0);
            int x=sc.nextInt();
            //先将x对应的结点在双链表中删除
            Node node=map.get(x);
            node.pre.next=node.next;
            node.next.pre=node.pre;
            if (c=='L')
                //将其插入到左端点
                node.next=head.next;
                head.next.pre=node;
                head.next=node;
                node.pre=head;
            else
                //将其插入到右端点
                node.pre=last.pre;
                last.pre.next=node;
                node.next=last;
                last.pre=node;
            
        
        pre=head.next;
        while (pre!=last)
            out.print(pre.v+" ");
            pre=pre.next;
        
        out.flush();
    
    static class Node
        int v;
        Node pre;
        Node next;
        public Node(int v, Node pre, Node next) 
            this.v = v;
            this.pre = pre;
            this.next = next;
        
    

C++

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> PII;
#define pb(s) push_back(s);
#define SZ(s) ((int)s.size());
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 200010;

int n, m;
void solve()

	cin >> n >> m;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++i) 
		a[i] = i;
	
	int l = 0, r = n - 1;
	string op;
	int x;
	for (int i = 0; i < m; ++i) 
		cin >> op >> x;
		x--;
		if (op == "L") a[x] = --l;
		else a[x] = ++r;
	
	std::vector<PII> b(n);
	for (int i = 0; i < n; ++i) 
		b[i] = a[i], i;
	
	sort(all(b));
	for (auto [x, y]: b) cout << y + 1 << " ";

int main()

	ios_base :: sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int t = 1;
	while (t--)
	
		solve();
	
	return 0;

第十三届蓝桥杯c++b组2022年国赛决赛题解

题目pdf下载十三届蓝桥杯c++b组2022国赛题目pdf下载

G题没有写,J题是暴力的,其他好像都写出来,但是估计还是有错的。

目录

正文:

试题 A: 2022

试题 B: 钟表

试题 C: 卡牌

试题 D: 最大数字

试题 E: 出差

试题 F: 费用报销

试题 G: 故障

试题 H: 机房

试题 I: 齿轮

试题 J: 搬砖

结尾:


正文:

试题 A: 2022

题意: 2022分为不同十个不同的正整数的情况数。

思路:动态规划,我的答案是:379187662194355221

        以为挺简单的,但是dfs写完连100都跑不出来,这题难度不简单,估计卡了不少人时间

后面暴力出了答案,从55开始有答案(因为最小的十个不同的正整数是:1,2,3,4...10,和是55),根据前10个数很像哈代-拉马努金拆分数列,然后求出来和后面的不一样,而且会炸long long,所以这个数列应该是错的。

动态规划思路:

       以后有空更

暴力代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int a=55;
int ans=0;
void dfs(int d,int sum,int pre)        //d是选的数量,sum是选的和,pre是上次选的点
    if(d==10)
        if(sum==a)
        ans++;
        return;
    
    for(int i=pre+1;i<=a;i++)
        if(i+sum<=a)
            dfs(d+1,sum+i,i);
        
    

int main()

    dfs(0,0,0);
    cout<<ans<<endl;
    return 0;

动态规划代码:

#include<stdio.h>
#include<string.h>
long long i,j,k,dp[50000][20];
int main()

    for(i=1;i<=2022;i++)
    
        for(j=2022;j>=1;j--)
        
            for(k=1;k<=9;k++)
            
                dp[j+i][k+1]=dp[j+i][k+1]+dp[j][k];
            
        
        dp[i][1]++;
    
//    for(i=1;i<=100;i++)
//        printf("%lld\\n",dp[i][10]);
    printf("%lld\\n",dp[2022][10]);
    return 0;


试题 B: 钟表

题意:一个钟表的时针、分针的角度差==分针、秒针的角度差,求此时的时分秒。

思路:暴力,我的答案是:4 48 0

        三个for起手不难,主要就是计算三个针的角度,

秒的角度就是:m/60

分的角度就是:f/60+(m/60)/*60,因为秒贡献的度数最多是1/60,贡献了m/60*(1/60)

时的角度就是:s/12+(f+m*60)/(60*12);,因为分钟贡献的度数最多是1/12,如果有res分钟,那么a=s/12+res/12

注意优弧劣弧的概念,小数的角度是<=0.5的。

代码:

#include<bits/stdc++.h>
using namespace std;
#define dou double
#define EXP 1e-6
#define M 100010
int main()

    for(dou s=0;s<=6;s++)
        for(dou f=0;f<60;f++)
            for(dou m=0;m<60;m++)
                dou a=s/12+(f+m*60)/(60*12);    //时针在表上角度
                dou b=f/60+m/(60*60);       //分针在表上角度
                dou c=m/60;             //秒针在表上角度
                dou x=fabs(a-b)>0.5?1-fabs(a-b):fabs(a-b);      //x是时针和分针夹角
                dou y=fabs(b-c)>0.5?1-fabs(b-c):fabs(b-c);      //x是分针和秒针夹角
                if(fabs(x-2*y)<EXP)                //如果A==2*B
                    cout<<s<<" "<<f<<" "<<m<<endl;
                
            
    return 0;


试题 C: 卡牌

 

 题意:a[i]数组是已有的 i 类手牌的数量,每个类(1-n类)的出1张可以组成一套,还有m张空白的,可以随便写成任意i类。b数组是该类最多被空白牌写成几张,求组成的最多套牌。

思路:二分

        容易知道是把空白牌用到少的类上,这题思路就是直接二分答案了

check函数:

int check(int mid)        //看看mid套行不行
    LL sum=0;
    for(int i=1;i<=n;i++)
        if(a[i]<mid)        //i类原来数量就超过mid张就不用考虑了
            if(mid-a[i]>b[i]) return 0;        //如果需要的比限制多返回NO
            sum+=mid-a[i];
            if(sum>m) return 0;        //如果使用空白牌多与m,返回NO
        
    
    return 1;

代码:

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define M 1000005
LL n,m;
LL a[M],b[M];
int check(int mid)
    LL sum=0;
    for(int i=1;i<=n;i++)
        if(a[i]<mid)
            if(mid-a[i]>b[i]) return 0;
            sum+=mid-a[i];
            if(sum>m) return 0;
        
    
    return 1;

int main()

    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(int i=1;i<=n;i++) scanf("%lld",&b[i]);
    LL l=0,r=n*n,ans=0;
    while(l<=r)
        LL mid=(l+r)/2;
        if(check(mid))
            l=mid+1;
            ans=mid;
        else
            r=mid-1;
        
    
    printf("%lld\\n",ans);
    return 0;


试题 D: 最大数字

 题意:给一个小于1e18的数字,不超过a次可以给一位+1,9再+就变成0,

不超过b次可以给一位-1,0再-变成9。

 思路:思维+暴力深搜(dfs)

        使用肯定是从前面开始的,因为是不超过多少次使用,前面就是能省则省,但是但凡有用,必须使用,暴力出答案即可。

dfs代码:

void dfs(LL a,LL ans,LL b,LL c)    //a表示当前的N,ans是10的某次方,表示数量级,b和c是剩余数量
    if(ans==0)
        maxx=max(maxx,a);        //更新答案
        return;
    
    int d=a/ans%10;
    if(b>9-d)                        //能变成9就变9,
        int r=b-(9-d);
        dfs(a+(9-d)*ans,ans/10,r,c);
    else                        //不能变成9就全用
        dfs(a+b*ans,ans/10,0,c);
    
    if(c!=0)
        if(c>=d+1)                //能变成9就用,不能变就省着
            int r=c-(d+1);
            dfs(a-d*ans+9*ans,ans/10,b,r);
        
    

代码:

#include<bits/stdc++.h>
using namespace std;
#define fo(a,b) for(int i=a;i<=b;i++)
#define inf 0x3f3f3f3f
#define LL long long
#define M 100010
LL a,b,c;
LL maxx=0;
void dfs(LL a,LL ans,LL b,LL c)
    if(ans==0)
        maxx=max(maxx,a);
        return;
    
    int d=a/ans%10;
    if(b>9-d)
        int r=b-(9-d);
        dfs(a+(9-d)*ans,ans/10,r,c);
    else
        dfs(a+b*ans,ans/10,0,c);
    
    if(c!=0)
        if(c>=d+1)
            int r=c-(d+1);
            dfs(a-d*ans+9*ans,ans/10,b,r);
        
    

int main()

    cin>>a>>b>>c;
    LL tmp=a;
    LL ans=1;
    while(a)
        a/=10;
        ans*=10;
    
    dfs(tmp,ans/10,b,c);
    cout<<maxx<<endl;
	return 0;


试题 E: 出差

 题意:n个点,m条边构成一个有边权的无向图,然后每个顶点都有自己的停留时间,即到达该点要停的时间,都是正数,求1到n点的最短时间

思路:最短路的贝尔曼-福特算法(Bellman-Ford)

        这题就是最短路模板题,只是加上了顶点要停留,感觉迪杰斯特拉算法(Dijkstra)应该也行,但觉得贝尔曼-福特算法(Bellman-Ford)应该更合适。

只是在使用边的时候,将边权+终点停留时间,终点为n时不加

更新代码:

 for(int k=1;k<=n;k++)        //n次更新
        for(int i=1;i<=m;i++)
            int res1=0,res2=0;
            if(b[i]!=n) res1=x[b[i]];        //终点不为n,边权+停留时间
            if(a[i]!=n) res2=x[a[i]];
            dist[b[i]]=min(dist[b[i]],dist[a[i]]+c[i]+res1);
            dist[a[i]]=min(dist[a[i]],dist[b[i]]+c[i]+res2);
        
    

代码:

#include<bits/stdc++.h>
using namespace std;
#define fo(a,b) for(int i=a;i<=b;i++)
#define inf 0x3f3f3f3f
#define LL long long
#define M 100005
int n,m;
int x[M];
int dist[M],a[M],b[M],c[M];
int main()
    scanf("%d%d",&n,&m);
    memset(dist,inf,sizeof(dist));
    dist[1]=0;
    for(int i=1;i<=n;i++) cin>>x[i];
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&a[i],&b[i],&c[i]);
    for(int k=1;k<=n;k++)
        for(int i=1;i<=m;i++)
            int res1=0,res2=0;
            if(b[i]!=n) res1=x[b[i]];
            if(a[i]!=n) res2=x[a[i]];
            dist[b[i]]=min(dist[b[i]],dist[a[i]]+c[i]+res1);
            dist[a[i]]=min(dist[a[i]],dist[b[i]]+c[i]+res2);
        
    
    cout<<dist[n]<<endl;
    return 0;


试题 F: 费用报销

 

 题意:给同一年的一些天,这些天都一个或多个的钱,选一些天使金额最多且不超多m,其中所有相邻的天数相差不超过k(>=k)

思路:动态规划

        比较简单得到动态规划,首先将天转变为一维数组,dp[i]表示该天最大的金额。

那么dp[i]=max(dp[i-1],dp[i-k]+a[i])                //对应的就是不选和选

核心代码:


    for(int i=1;i<=500;i++)            //一年365天,dp超过365就行
        if(dp[i]+dp[i-k]<=m)
            dp[i]=max(dp[i]+dp[i-k],dp[i-1]);
        else        //如果选了会超过m,就不选了
            dp[i]=dp[i-1];
    

代码:

#include<bits/stdc++.h>
using namespace std;
#define fo(a,b) for(int i=a;i<=b;i++)
#define inf 0x3f3f3f3f
#define LL long long
#define M 100005
int n,m,k;
int x,y,z;
int mp[105][105],dp[10005];
int r[]=0,31,28,31,30,31,30,31,31,30,31,30,31;
int main()
    int sum=0;
    for(int i=1;i<=12;i++)
        for(int j=1;j<=r[i];j++)
            sum++;
            mp[i][j]=sum;        //映射天数
        
    
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
        scanf("%d%d%d",&x,&y,&z);
        dp[mp[x][y]]=max(dp[mp[x][y]],z);
    
    for(int i=1;i<=500;i++)
        if(dp[i]+dp[i-k]<=m)
            dp[i]=max(dp[i]+dp[i-k],dp[i-1]);
        else
            dp[i]=dp[i-1];
    
    cout<<dp[500]<<endl;
    return 0;


试题 G: 故障

 题意:不知

思路:不知,题有点多,做不过来

代码:未有


试题 H: 机房

 题意:给一颗无边权的树,查询m次两点路劲之间,所有点的直接连接点的数量和。

 思路:LCA+树形DP

        还是比较好想的,dfs处理出给个点的直接连接点的数量,再dfs,求出每个点到顶点的直接连接点的数量的前缀和,用dp[i]表示。

d表示两点x和y的LCA(共公祖先),pre[d]表示d的父点,结果就是dp[x]+dp[y]-dp[d]-dp[pre[d]],其中

核心代码:

void dfs(int d,int pre,int sum)

    for(int i=1;i<n+5;i++) lg[i]=lg[i-1]+(1<<lg[i-1]==i);           //LCA倍增
    fa[d][0]=pre;                   //LCA倍增
    h[d]=h[pre]+1;                  //LCA倍增
    p[d]=pre;                  //父点
    for(int i=1;i<=lg[h[d]]+1;i++)                  //LCA倍增
        fa[d][i]=fa[fa[d][i-1]][i-1];
    int l=v[d].size();                    //l也是当前结点直接连接其他结点数量
    dp[d]=l+sum;                          //sum是之前父链的和
    fo(0,l-1)
        int now=v[d][i];
        if(pre!=now)
            dfs(now,d,dp[d]);
        
    

代码:

#include<bits/stdc++.h>
using namespace std;
#define fo(a,b) for(int i=a;i<=b;i++)
#define inf 0x3f3f3f3f
#define LL long long
#define M 200005
int n,m,x,y;
int dp[M],p[M];
vector<int>v[M];
int h[M],lg[M],fa[M][35];
void dfs(int d,int pre,int sum)

    for(int i=1;i<n+5;i++) lg[i]=lg[i-1]+(1<<lg[i-1]==i);           //LCA倍增
    fa[d][0]=pre;                   //LCA倍增
    h[d]=h[pre]+1;                  //LCA倍增
    p[d]=pre;                  //父点
    for(int i=1;i<=lg[h[d]]+1;i++)                  //LCA倍增
        fa[d][i]=fa[fa[d][i-1]][i-1];
    int l=v[d].size();                    //l也是当前结点直接连接其他结点数量
    dp[d]=l+sum;                          //sum是之前父链的和
    fo(0,l-1)
        int now=v[d][i];
        if(pre!=now)
            dfs(now,d,dp[d]);
        
    

int LCA(int a,int b)

    if(h[a]<h[b]) swap(a,b);
    for(int i=lg[h[a]]+1;i>=0;i--)
        if(h[a]-(1<<i)>=h[b])
            a=fa[a][i];
    
    if(a==b) return a;
    for(int i=lg[h[a]]+1;i>=0;i--)
        if(fa[a][i]!=fa[b][i])
            a=fa[a][i];
            b=fa[b][i];
        
    return fa[a][0];

int main()
    cin>>n>>m;
    fo(1,n-1)
        cin>>x>>y;
        v[x].push_back(y);
        v[y].push_back(x);
    
    dfs(1,0,0);
    while(m--)
        int x,y;
        cin>>x>>y;
        int d=LCA(x,y);
        cout<<dp[x]+dp[y]-dp[d]-dp[p[d]]<<endl;
    
    return 0;


试题 I: 齿轮

 题意:给一个数组为齿轮大小,问能不能换顺序后,尾转的速度是首转的速度的qi倍,询问Q次。

思路:不难发现这个中间的没有用,就是首的半径=尾的半径*qi就可。而且这种排序是随便的,只需要找这个数组中没有两个数相除==qi即可。

那么需处理出这个数组所有的可有倍数即可。具体看代码更容易理解,这个时间复杂度是n*logn的,对1e6也应该能用,注意倍数1的判断

预处理代码:

for(int i=1;i<=MAX;i++)            //MAX=2e5
        if(vis[i]==1)                //vis[i]表示i在该数组中
            for(int j=i*2;j<=MAX;j+=i)
                if(vis[j]==1) ans[j/i]=1;        //ans是结果数组
                
        
    

代码:

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define LL long long
#define M 1000005
int MAX=400005;
int n,m,flag=0;
int a[M];
int vis[M],ans[M];
int main()
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>a[i];
        if(vis[a[i]]==1) flag=1;        //单独判断ans[1]
        vis[a[i]]=1;            //表明数组有这个数
    
    if(flag) ans[1]=1;
    for(int i=1;i<=MAX;i++)
        if(vis[i]==1)
            for(int j=i*2;j<=MAX;j+=i)
                if(vis[j]==1) ans[j/i]=1;
            
        
    
    int x;
    while(m--)
        cin>>x;
        if(ans[x]) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    
    return 0;


试题 J: 搬砖

 题意:选取若干个从上到下放,重量不能小于上面的和,求总价值最大

思路:可能是动态规划,写差不多觉得和题意有点出入,就直接dfs暴力

暴力挺简单的,先结构体排序,重量小的一定先选在上面,不然直接压垮了。然后同重量的价值大的一定先选。

dfs出所有的1-n排序,也就是:

1 2 3 4 5

1 2 3 4

3 4 5

2 4 5

这些

....

然后计算判断更新最后答案

代码:

#include<bits/stdc++.h>
using namespace std;
#define fo(a,b) for(int i=a;i<=b;i++)
#define inf 0x3f3f3f3f
#define LL long long
#define M 200005
int n,maxx=0;
struct Node

    int a,b;
    bool operator<(const Node temp)const
        if(a==temp.a) return b>temp.b;
        return a<temp.a;
    
x[M];

//此代码是暴力代码,只能过30%

int q[M],v=0;
void dfs(int d,int pre)
    if(d==n)                //判断q数组中的顺序是否合法
        int sum=0,ans=0;
        for(int i=1;i<=v;i++)
            if(x[q[i]].a<sum) break;
            sum+=x[q[i]].a;
            ans+=x[q[i]].b;
            if(i==v) maxx=max(maxx,ans);
        
        return;
    
    for(int i=pre+1;i<=n;i++)
        q[++v]=i;
        dfs(d+1,i);
        v--;
    
    if(v!=0) dfs(d+1,pre);

int main()
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>x[i].a>>x[i].b;
    sort(x+1,x+n+1);
    dfs(0,0);
    cout<<maxx<<endl;
    return 0;

结尾:

        看了下演草纸,才用了1页多,一般比赛要好几页的。不少题是算法及相关的题,总体acm选手估计是叫好,但是对其他选手不清楚了,这题个人觉得难度适中,因为往年很多题不能暴力,而且到现在,那些题也没有题解(csdn上)。今年只有一题没看,一个暴力,难度肯定是降了不少的。

开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系

以上是关于第十三届蓝桥杯Java B 组国赛 C 题——左移右移(AC)的主要内容,如果未能解决你的问题,请参考以下文章

蓝桥杯第十三届Web组国赛天气趋势A详细题解

第十三届蓝桥杯c++b组2022年国赛决赛题解

蓝桥杯Web2022年第十三届蓝桥杯Web大学组国赛真题解析

第十三届蓝桥杯国赛 C++ C组 F 题Python B组 E 题——近似GCD(AC)

第十三届蓝桥杯国赛 C++ C 组 Java A 组 C 组 Python C 组 E 题——斐波那契数组(三语言代码AC)

第十三届蓝桥杯国赛真题 PythonB组 复盘以及获奖感言(国一!!!)