长沙学院2022蓝桥杯模拟赛一

长沙学院2022蓝桥杯模拟赛一_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJhttps://ac.nowcoder.com/acm/contest/26010#question

目录

A.因子和

B.找数

C.数与连分数

D.文件查找

E.多米诺骨牌

F.财富分配

G.分数鄙视


A.因子和


我提供一个类似于埃氏筛的思路,例如有1,2,3,4四个数,最大是4,我们列举出小于4的每个数的倍数,那么这个倍数就一定含有乘以倍数之前的因数,例如1的四倍是4,那么4就必定含有1这个因数.我们另外开一个数组来记录它们的和即可,含有这个因子就把因子加进对应位置去.

#include<iostream>
#define ll long long
using namespace std;
ll sum[1000006];
void init(int n)
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j*i<=n;j++)
        {
            sum[i*j]+=i;
        }
    }
}
int main()
{
	int i,j,t,n;
	cin>>n;
    init(n);
    for(int i=1;i<=n;i++)
        cout<<sum[i]<<" ";
    cout<<endl;
	return 0;
}


B.找数

 


此题分两种情况讨论,如果两个数数位相同,例如都是三位数,我们可以列出它的方程为f(x)=x*(999-x)这个时候就会发现这是一个开口向下的二次函数,取最大值的时候就是499和500的时候,以此类推可以得出1结论,同位的数,越是接近中间,值也就越大,那么我们只用比较两个数对于中心距离的大小1即可.如果不是同位的数的话就针对大的数进行讨论1,观察函数在它的范围内取最大值即为答案.

#include<iostream>
#include<cmath>
#define int long long
using namespace std;
int change(int x)
{
    int wei=1,tran=0;
    int z=x;
    while(x)
    {
        tran+=wei*(9-x%10);
        wei*=10;
        x/=10;
    }
    return tran*z;
}
signed main()
{
    int l,r,maxx=0,weil=0,weir=0,ll,rr,tt=4;
    scanf("%lld%lld",&l,&r);
    ll=l;
    rr=r;
    while(ll)
    {
        ll/=10;
        weil++;
    }
    while(rr)
    {
        rr/=10;
        weir++;
    }
    if(weir>weil)
    {
        tt=pow(10,weir)/2-1;
        if(r<tt)
        {
            printf("%lld",change(r));
        }
        else
        {
            printf("%lld",change(tt));
        }
    }
    else if(weir==weil)
    {
            tt=pow(10,weir)/2-1;
            if(l<tt&&r<=tt)
            {
                printf("%lld",change(r));
            }
            else if(l<=tt&&r>=tt)
            {
                printf("%lld",change(tt));
            }
            else if(l>tt&&r>tt)
            {
                printf("%lld",change(l));
            }
    }
    return 0;
}

C.数与连分数


纯模拟题,先把这两个的转换的方法自己算一遍,然后尝试去用代码实现,注意一些特殊情况.

针对于连分数转分数,我们就用两个变量来储存分子和分母,这样来模拟它的加法.

针对于分数转化为连分数就有挺多坑的,一定要在进行模拟的时候进行分子分母的约分,约分了之后注意能否除尽这些情况.另外就是注意输出结果中 ; 和 , 的情况.

#include<iostream>
#include<algorithm>
#include<string>
#define int long long
using namespace std;
string str;
int arr[500];
void change1()
{
    int k,cnt1,cnt2,cnt3=1,id=0,temp=0;
    for(int i=1;i<str.size();i++)
    {
        if(str[i]>='0'&&str[i]<='9')
        {
            temp=temp*10+str[i]-'0';
        }
        else
        {
            arr[id]=temp;
            temp=0;
            id++;
        }
    }
    if(id==1)
    {

        printf("%lld\n",arr[0]);
        return ;
    }
    cnt1=arr[id-1];
    for(int i=id-2;i>=0;i--)
    {
        cnt2=arr[i];
        cnt3=cnt2*cnt1+cnt3;
        k=cnt3;
        cnt3=cnt1;
        cnt1=k;
    }
    if(cnt3==1)
    {
        printf("%lld\n",cnt1);
        return;
    }
    printf("%lld/%lld\n",cnt1,cnt3);
}
void change2()
{
    int k,temp=0,cnt1,cnt2,cnt3,id=0;
    for(int i=0;i<str.size();i++)
    {
        if(str[i]>='0'&&str[i]<='9')
        {
           temp=temp*10+str[i]-'0';
        }
        else 
        {
            cnt1=temp;
            temp=0;
        }
    }
    cnt2=temp;
    int gc=__gcd(cnt1,cnt2);
        cnt1/=gc;
        cnt2/=gc;
    printf("[");
    int ma=0;
    while(cnt1%cnt2!=1)
    {
        if(cnt1%cnt2==0)
        {
            printf("%lld]\n",cnt1/cnt2);
            return;
        }
        int gc=__gcd(cnt1,cnt2);
        cnt1/=gc;
        cnt2/=gc;
        printf("%lld",cnt1/cnt2);
        if(ma==0)
            printf(";"),ma=1;
        else
            printf(",");
        cnt1=cnt1%cnt2;
        k=cnt1;
        cnt1=cnt2;
        cnt2=k;
    }
    printf("%lld,%lld",cnt1/cnt2,cnt2);
    printf("]\n");
}
signed main()
{
    while(cin>>str)
    {
        if(str[0]=='[')
            change1();
        else
            change2();
    }
    return 0;
}

D.文件查找

 


我用的方法是dfs+双端队列模拟栈(不一定要用双端队列,只需要一个能够实现遍历和模拟栈的储存方式的东西就行,可以尝试数组模拟,推荐去学一下stl容器).题目中存在3种变量,文件夹,文件数,文件名,所以要注意,我们每次输入是判断此时是文件名还是文件,题中说过了,文件名含有一个".",可以依次来区别两者,然后输入文件数进行递归,每次递归进去了就用双端队列去把当前的文件夹名存进去,一直如此,直到遍历到了文件,判断文件和所给的需要查找的文件是不是一样的,是一样的就遍历,把存在双端队列模拟的栈中的文件夹名取出来输出.然后在返回上一层,返回上一层递归时要注意此时已经退出了此文件夹的话,要把文件夹名从双端队列模拟的栈中取出.

对该样例分析

nanqiao.cpp
nanqiao 2
ccsu 1
nanqiao.cpp
nanqiao 1
ccsu.cpp

首先我们输入了nanqiao,因为不含有".",那么就把它存进双端队列模拟的栈内,在进行数字输入,再到ccsu,因为是文件夹,存进栈内,再递归下一层,会发现下一个就是我们要找的nanqiao.cpp.就把模拟栈内的元素全部输出......

#include<iostream>
#include<string>
#include<deque>
using namespace std;
string str,ss;
deque<string>sta;
void dfs(int x)
{
    string s1;
    int xx;
    while(x>0)
    {
        x--;
        cin>>s1;
        int mark=0;
        for(int i=0;i<s1.size();i++)
        {
            if(s1[i]=='.')
            {
                mark=1;
            }
        }
        if(mark==1)
        {
            if(s1==str)
            {
                for(int j=0;j<sta.size();j++)
                {
                    cout<<sta[j]<<"\\";
                }   
                cout<<s1<<endl;
            }
        }
        else
        {
            sta.push_back(s1);
            cin>>xx;
            dfs(xx);
            sta.pop_back();
        }
    }
    return ;
}
int main()
{
    int t;
    cin>>str;
    while(cin>>ss)
    {
        int mark=0;
        for(int i=0;i<ss.size();i++)
        {
            if(ss[i]=='.')
            {
                mark=1;
            }
        }
        if(mark==1)
        {
            if(ss==str)
                cout<<ss<<endl;
        }
        else
        {
            sta.push_back(ss);
            cin>>t;
            dfs(t);
            sta.pop_back();
        }
    }
    return 0;
}

E.多米诺骨牌

 

这题挺多做法的,普遍的就是bfs和模拟,我采用的模拟做法.

因为只有两种会改变其他骨牌形态的骨牌:"\""/".那我们就枚举所有的两种的情况组合来进行处理.就先遍历字符串,当此时遍历的字符不是"|"的时候用l(左端),r(右端)进行记录最开始遍历到的两个的左右端点形态,然后针对它们的形态来进行一段段的处理,例如如果是"/""\"那么中间的牌都会向中间倒下,我这里用了一个区间处理,如果此区间牌数为奇数就会有一张牌保持"|"状态.把这些牌的情况处理完之后,再用l(左端)来记录此时r(右端)的下标和形态,(此时的r(右端)会和后面第一个非"|"的牌产生效果).这样一连串处理完之后,还要注意.如果从左起最先遍历到的非"|"骨牌是"\",那么从一开始到这张牌都要变为"\",同理,最后一张非"|"如果是"/"也会影响这张牌到最后面的情况.

然后就是转义字符,'\\'才能表示出\.

#include<iostream>
#include<cstring>
using namespace std;
struct node
{
    char ty;
    int idx;
}l,r;
char str[10000007];
int main()
{
    scanf("%s",str);
    int len=strlen(str);
    int cnt=0;
    for(int i=0;i<len;i++)
    {
        if(str[i]!='|'&&cnt==0)
        {
            l.ty=str[i];
            l.idx=i;
            cnt=1;
        }
        else if(str[i]!='|'&&cnt==1)
        {
            r.ty=str[i];
            r.idx=i;
            cnt=2;
        }
        if(cnt==2)
        {
            if(l.ty=='/'&&r.ty=='/')
            {
                for(int j=l.idx;j<=r.idx;j++)
                    str[j]='/';
                l=r;
                cnt=1;
            }
            else if(l.ty=='/'&&r.ty=='\\')
            {
                int ll=r.idx-l.idx-1;
                ll/=2;
                for(int j=l.idx;j<=l.idx+ll;j++)
                    str[j]='/';
                for(int j=r.idx;j>=r.idx-ll;j--)
                    str[j]='\\';    
                l=r;
                cnt=1;
            }
            else if(l.ty=='\\'&&r.ty=='\\')
            {
                for(int j=l.idx;j<=r.idx;j++)
                    str[j]='\\';
                l=r;
                cnt=1;
            }
            else if(l.ty=='\\'&&r.ty=='/')
            {
                l=r;
                cnt=1;
            }
        }
    }
    for(int i=0;i<len;i++)
    {
        if(str[i]=='/')
            break;
        else if(str[i]=='\\')
        {
            for(int j=0;j<=i;j++)
            {
                str[j]='\\';
            }
            break;
        }
    }
    for(int i=len-1;i>=0;i--)
    {
        if(str[i]=='\\')
            break;
        else if(str[i]=='/')
        {
            for(int j=i;j<len;j++)
            {
                str[j]='/';
            }
            break;
        }
    }
    for(int i=0;i<len;i++)
        printf("%c",str[i]);
    return 0;
}

F.财富分配

 


涉及到了运算符号重载和优先队列.

先定义一个结构体

struct point 
{
    int a,b,x;
    int power,ans;
}

我们用

bool operator <(const point that)const
    {
        return this->power<=that.power;
    }

来重载运算符"<".(基本格式是这样),这样就可以让结构体的<运算具有意义,然后在定义一个优先队列来储存结构体,结构体就会按照这个小于号来进行排序.

基本思路就是把m块钱一元一元的分给每个人,但是每次都按贪心的思想,分给亏损增长最少的人,那么就是用这个人分了一块钱的情况减去上没有分到这一块钱的情况的大小来维护优先队列:

f(x+1)-f(x),这就是分了一块钱之后这个人的亏损增长值.

power=-a*(int)pow(x+1-b,2)+a*(int)pow(x-b,2);

每次优先队列首一定是增长最少的,那么我们就它从优先队列里面取出来,把钱数+1,在放回优先队列中,保证每一块钱分给一个人之后,亏损增长都是最小的

#include<iostream>
#include<cmath>
#include<queue>
#define int long long
using namespace std;
struct point 
{
    int a,b,x;
    int power,ans;
    bool operator <(const point that)const
    {
        return this->power<=that.power;
    }
    void get_power()
    {
        power=-a*(int)pow(x+1-b,2)+a*(int)pow(x-b,2);
    }
    void get_ans()
    {
        ans=-a*(int)pow(x-b,2);
    }
}arr[100005];
signed main()
{
    int n,m,Ans=0;
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%lld",&arr[i].a);
    priority_queue<point>qu;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&arr[i].b);
        arr[i].x=0;
        arr[i].get_power();
        qu.push(arr[i]);
    }
    point temp;
    while(m--)
    {
        temp=qu.top();
        qu.pop();
        temp.x++;
        temp.get_power();
        qu.push(temp);
    }
    while(qu.size())
    {
        temp=qu.top();
        qu.pop();
        temp.get_ans();
        Ans+=temp.ans;
    }
    printf("%lld",Ans);
    return 0;
}

G.分数鄙视

 


稍微有改变的逆序对板子题.对于样例:

4
1 3 3 5

按照正常情况,zzf学长所得的分数应该是:

1 2 3 4

我们尝试用现在得分来减去他的应有得分得到:

0 1 0 1

你会发现第二次和第四次超出了他的预期,那么它们就不会互相鄙视,因为它们是同一水平的,但是第二次会鄙视第一次,因为第二次的水平比第一次的水平高1等.也就是如果按照第二次的标准,第一次得分应该是2,但是它比而小,这样就产生了一次鄙视.这就是逆序对的板子了,然后我就去套树状数组板子了,可以尝试学逆序对的解法.

#include<iostream>
using namespace std;
int a[100005];
int vis[1000006];
int maxx=0;
int lowbits(int x)
{
    return x-(x&(x-1));
}
int query(int x)
{
    int res=0;
    while(x)
    {
        res+=vis[x];
        x-=lowbits(x);
    }
    return res;
}
void add(int x)
{
    while(x<=maxx)
    {
        vis[x]+=1;
        x+=lowbits(x);
    }
}
int main()
{
    int n,ans=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        a[i]=a[i]-i+100000;
        maxx=max(a[i],maxx);
    }   
    for(int i=1;i<=n;i++)
    {
        ans=(ans+query(a[i]-1))%12345;
        add(a[i]);
    }
    printf("%d",ans);
    return 0;
}

H.幸福的道路

不会树形dp的我已经可以重开了.

 

以下为学习树形dp之后补的坑:

思路:

解决该题分两步,先用树形dp去求他的每一天的幸福值,然后在求出每一天幸福值的前提下,按照题目条件用单调队列去求能保证幸福值波动在M的区间长度.

首先是树形dp求每一天幸福值.如果我们求一个点到其他点的最大值,可以用dfs去遍历并且维护这个点的最大的幸福值和次大幸福值.更新父节点的这两个值时,需要用子节点去对他进行更新,(为叶子节点此时次大最大都为0).将子节点中最大值加上两个点的距离,去更新父节点已经存在的次大最大值,并且开一个数组记录每个父节点最大值所在的子节点是哪一个.第二遍dfs则是用父节点去更新子节点.当子节点在父节点的最大值的路径上时,用次大值和两者距离去更新最终贡献,而不在最大值路径时,用父节点最大值和距离去更新,判断是否在最大值路径上前面已经记录过了.

我们用树形dp已经解决了每一天的幸福值后.采用双指针去求区间,并且用两个答案掉队列去维护它.两个单调队列分别维护该区间的最大值和最小值,随着合右指针右移,当两个单调队列的队首相差大于M之后则将序号在前面的点出队,每次取区间最长长度即可.

#include<iostream>
#include<cstring>
using namespace std;
int n,m;
struct node
{
    int to,val,ne;
}edge[2000006];
int h[1000006],tot=0,sum[1000006];
int d1[1000006],d2[1000006],name[1000006];
int qmax[1000006],qmin[1000006];
void add(int x,int y,int val)
{
    edge[++tot].to=y;
    edge[tot].val=val;
    edge[tot].ne=h[x];
    h[x]=tot;
}
void dfs_down(int x,int fa)
{   
    d1[x]=0,d2[x]=0;
    for(int i=h[x];i!=-1;i=edge[i].ne)
    {
        int y=edge[i].to;
        if(y==fa)
            continue;
        dfs_down(y,x);
        int d=d1[y]+edge[i].val;
        if(d>=d1[x])
        {
            d2[x]=d1[x];
            d1[x]=d;
            name[x]=y;
        }
        else if(d>=d2[x])
            d2[x]=d;
    }
    return ;
}   
void dfs_up(int x,int fa)
{
    for(int i=h[x];i!=-1;i=edge[i].ne)
    {
        int y=edge[i].to;
        if(y==fa)
            continue;
        if(y==name[x])
            sum[y]=max(d2[x],sum[x])+edge[i].val;
        else
            sum[y]=max(d1[x],sum[x])+edge[i].val;
        dfs_up(y,x);
    }
    return ;
}
int main()
{
    int x,y;
    memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    for(int i=2;i<=n;i++)
    {
        scanf("%d%d",&x,&y);
        add(i,x,y);
        add(x,i,y);
    }
    dfs_down(1,0);
    dfs_up(1,0);
    for(int i=1;i<=n;i++)
        sum[i]=max(sum[i],d1[i]);
    int len=0,l=1,hmax=0,tmax=-1,tmin=-1,hmin=0;
    for(int r=1;r<=n;r++)
    {
        while(tmax>=hmax&&sum[qmax[tmax]]<=sum[r])
            tmax--;
        qmax[++tmax]=r;
        while(tmin>=hmin&&sum[qmin[tmin]]>=sum[r])
            tmin--;       
        qmin[++tmin]=r;
        while(sum[qmax[hmax]]-sum[qmin[hmin]]>m)
        {
            l=min(qmax[hmax],qmin[hmin]);
            l++;
            if(tmax>=hmax&&l>qmax[hmax])hmax++;
            if(tmin>=hmin&&l>qmin[hmin])hmin++;
        }
        len=max(len,r-l+1);
    }
    printf("%d",len);
    return 0;
}

总结:本场比赛以模拟为主,涉及少量算法和stl容器.dp也没有出简单题,贪心也出的比较难.

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值