北京信息科技大学第十三届程序设计竞赛暨ACM选拔赛(重现赛)题解

题目链接:

北京信息科技大学第十三届程序设计竞赛暨ACM选拔赛(重现赛)_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ大学ACM校赛新生赛是面向ACM/ICPC/CCPC/区域赛校队选手,巩固经典专题的编程比赛,ACM入门训练,大学ACM校队选拔比赛。https://ac.nowcoder.com/acm/contest/20278#question这套题涉及的算法并不多(除了I题需要采用容斥和莫比乌斯),其余的都是简单的算法,更重考察的就是做题的思维能力.

A.lzh的蹦床

这道题考察的就是做题的思维,仔细读题,我们针对每一位上的值进行分析.

首先就要明确,我们的目的是把每个数都变到1,而该点的值可能会受到前面所有能够跳到这个点的跳跃的影响.并且把影响转移给后面的一段区间,也就可以把这个题转换为区间的修改了.此时就要分情况讨论:(设此时到达的点的位置为i,我们以sum数组(差分数组)作为在从前面有多少次跳跃会使我们到达i点)

(1)当a[i]<=sum[i]的时候,此时说明当a[i]跳了a[i-1]次,已经变为1了,但是从前面跳过来的次数还没有消耗完,那么除了对后面a[i+1]到a[n]这个区间进行+1的修改之外,还要把剩余没有跳完的次数转移到a[i+1]上去:

 sum[i+1]+=(sum[i]-a[i]);
 sum[i+2]-=(sum[i]-a[i]);
 sum[i+1]++;
 sum[min(i+a[i],n)+1]--;

(2)当a[i]>sum[i]时,可以肯定的是,我从前面跳过来的次数消耗完了,我的a[i]值还没有变为1,此时就要对该点进行额外处理,也就是我们要以这里为起点跳跃,把它的值消耗到为1,也就是为结果做出贡献:

ans+=(a[i]-sum[i]-1);

然后就是对sum[i]次能够进行跳跃的区间进行处理(注意边界问题,a[i]的值范围小于1e9,可能会数组越界):

ll l=i+a[i]-sum[i]+1,r=i+a[i];//计算左右边界
if(i+2<=n)//进行边界处理
{
    sum[i+2]++;
    sum[min(n,r)+1]--;
}

这个题要思维,我去问了学长才会写,写的脑瓜子嗡嗡的.下面是全部代码:

#include<iostream>
#include<cstring>
#define ll long long
#define lmax 5005
using namespace std;
ll sum[lmax],a[lmax];
int main()
{
    ll t,n,ans,i;
    scanf("%lld",&t);
    while(t--)
    {
        ans=0;
        memset(sum,0,sizeof(sum));
        scanf("%lld",&n);
        for( i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        for( i=1;i<=n;i++)
        {
            sum[i]+=sum[i-1];
            if(sum[i]>=a[i])
            {
                sum[i+1]+=(sum[i]-a[i]);
                sum[i+2]-=(sum[i]-a[i]);
                sum[i+1]++;
                sum[min(i+a[i],n)+1]--;
            }
            else if(sum[i]<a[i])
            {
                if(sum[i]==0)
                {
                    ans+=a[i]-1;
                    sum[i+2]++;
                    sum[min(n,i+a[i])+1]--;
                }
                else
                {
                    ll l=i+a[i]-sum[i]+1,r=i+a[i];
                    if(i+2<=n)
                    {
                        sum[i+2]++;
                        sum[min(n,r)+1]--;
                    }
                    ans+=(a[i]-sum[i]-1);
                }
            }
        }
    printf("%lld\n",ans);
    }
    return 0;
}

B.所谓过河

该题有两种写法,dfs和并查集,dfs思路就是当存在点于岸边相交,从该点开始dfs找下一个石头,看结尾的石头能不能到达对岸,可以用一个标记数组记录走过了的,剪枝.我采用的第二种,并查集.就是从前往后遍历(我进行了一个点的位置排序,这样可以尽可能使靠近河岸的点作为根节点).将所有的点两两匹配(记得剪枝,两个点直接只用匹配一次),看能不能连接(比较两点距离和两个圆半径之和),能连接就连起来,遍历链接完所有两点组合了.再去遍历单个点,看该点的根节点(因为有排序,必定是和河岸相邻的点)和该点间的距离再加上两点的半径之和是不是大于河的宽度的,若大于就是可以过河的.

上代码:

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int fa[10005];
struct node
{
    long long x,y,r;
}poi[10005];
bool cmp(node a,node b)
{
    if(a.x!=b.x)return a.x<b.x;
    else return a.y<b.y;
};
int find(int x)     				
{
    if(fa[x]==x)return x;		
    return fa[x]=find(fa[x]);	
}
void merge(int x,int y)                     
{
    int fx=find(x),fy=find(y);            
    if(fx!=fy)                          
        fa[fx]=fy;                        
}
int jud(int a,int b)
{
    double an=pow((poi[a].x-poi[b].x),2)+pow((poi[a].y-poi[b].y),2);
    double qwq=pow((poi[a].r+poi[b].r),2);
    if(an<=qwq)return 1;
    else return 0;
}
int main()
{
    int n,h;
    scanf("%d%d",&n,&h);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&poi[i].x,&poi[i].y,&poi[i].r);
    }
    sort(poi+1,poi+1+n,cmp);
    for(int i=1;i<=n;i++)
    {
        for(int j=i;j<=n;j++)
        {
            if(jud(j,i)==1)
            {
                merge(j,i);
            }    
        }
    }
    int mark=0;
    for(int i=1;i<=n;i++)
    {
        int fi=find(i);
        if(abs(poi[fi].y-poi[i].y)+poi[fi].r+poi[i].r>=h)
        {
            
            mark=1;
            break;
        }
    }
    if(mark==1)printf("Yes");
    else printf("No");
    return 0;
}

C.旅行家问题1

直接分情况讨论旅行家的位置.

#include<iostream>
using namespace std;
int main()
{
    long long t,maxx=-1000000001,minn=1000000001,n,sta;
    scanf("%lld%lld",&n,&sta);
    for(int i=0;i<n;i++)
    {
        scanf("%lld",&t);
        t<minn?minn=t:t;
        t>maxx?maxx=t:t;
    }
    long long sum;
    if(sta>=minn&&sta<=maxx)
        sum=(min(sta-minn,maxx-sta))+maxx-minn;
    else if(sta<minn) sum=maxx-sta;
    else if(sta>maxx) sum=sta-minn;
    printf("%lld",sum);
    return 0;
}

D.旅行家的问题2

这个题看上去很简单,实际上还是要稍微动一下脑子.这是个贪心问题,就取决于最佳策略的选择.我们知道,上山的情况的话可能用梯子,可能坐飞机,而下山只能走梯子.然后就是其实起点的位置无所谓,题目要求回到出发的山,那么每座山的进出情况都是进一次出一次,就相当于一个环,起点就不必在意了.最好的策略就是我们每次都尽量往最高处走(取该处高度加上梯子长度不能到达的第一座山,坐飞机上去),先走到最高峰(可能飞机可能梯子),然后再一直走梯子往海拔低的山走.

我们把山按照海拔从小到大排序,先假设我每次都是走的梯子,求出和来,然后遍历,记录当前能够到达的最高的高度maxx=max(maxx,arr[i].h+arr[i].x),当遍历的山高度比maxx大时,请求乘坐直升机,给结果加上h[i]-maxx.

上代码:

#include<iostream>
#include<algorithm>
using namespace std;
struct node
{
    long long h,x;
}arr[100005];
bool cmp(node a,node b)
{
    return a.h<b.h;
}
int main()
{
    long long maxx,ans=0,n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&arr[i].h,&arr[i].x);
        ans+=arr[i].x;
    }
    sort(arr+1,arr+n+1,cmp);
    maxx=arr[1].h+arr[1].x;
    for(int i=2;i<=n;i++)
    {
        if(arr[i].h>maxx)ans+=(arr[i].h-maxx);
        maxx=max(maxx,arr[i].h+arr[i].x);
    }
    printf("%lld",ans);
    return 0;
}

E.小菲和Fib数列

先求用记忆化搜索出fib数列,这里fib的值只取0和1,因为(xi*xj+1)会进行对2取模.那么结果就很好算了,只需要记录数组中0的个数sum0和1的个数sum1,当xi和xj有任意一个为0时,就会对结果产生贡献,就计算1,0(sum0*sum1)和0,0(sum0*(sum0-1)/2)的情况数,就是结果.

上代码:

#include<iostream>
using namespace std;
int fib[50001];
int fib1(int x)
{
    if(x==1||x==2)return fib[x]=1;
    else if(fib[x]!=0)return fib[x]%2;
    else
    {
        return fib[x]=(fib1(x-1)+fib1(x-2))%2;
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)fib1(i);
    long long sum=0;
    int sum0=0,sum1=0;
    for(int i=1;i<=n;i++)
    {
        if(fib[i]==1)sum1++;
        if(fib[i]==0)sum0++;
    }
    printf("%lld",sum1*sum0+sum0*(sum0-1)/2);
    return 0;
}

F.好玩的音乐游戏

硬模拟,我这里用'-'连接'X','O'这些字符用了dfs,当遍历到了'O'或者'X',搜索它所在的这行后面有没有'O'或者'X',有的话就把中间的空格全部变成'-'.

上代码:

#include<iostream>
#include<string>
#include<cstring>
using namespace std;
char q[2005][9];
int dfs(int i,int j)
{
    for(int m=j+1;m<9;m++)
    {
        if(q[i][m]=='O'||q[i][m]=='X')
        {
            return m;
        }
    }
    return j;
}
int main()
{
    string str;
    int n,m,a,b;
    memset(q,' ',sizeof q);
    q[0][0]='+';
    q[0][8]='+';
    for(int i=1;i<=7;i++)q[0][i]='-';
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        q[i][0]='|';
        q[i][8]='|';
    }
    while(n--)
    {
        cin>>str;
        if(str=="tap")
        {
            scanf("%d%d",&a,&b);
            q[a][b]='O';
        }
        else if(str=="flick")
        {
            scanf("%d%d",&a,&b);
            q[a][b]='X';
        }
    }
    int t;
    for(int i=m;i>=0;i--)
    {
        for(int j=0;j<9;j++)
        {
            if(q[i][j]=='O'||q[i][j]=='X')
            {
                t=dfs(i,j);
                for(int k=j+1;k<t;k++)
                    q[i][k]='-';
            }
        }
    }
    for(int i=m;i>=0;i--)
    {
        for(int j=0;j<9;j++)
        {
            printf("%c",q[i][j]);
        }
        printf("\n");
    }
    return 0;
}

G.ranko的手表

暴力加筛法,遍历当前时刻和该时刻之后所有时刻,将不符合题目意思的筛掉,符合条件的计算最值即可.

#include<iostream>
#include<cmath>
using namespace std;
char t1[6],t2[6];
int t11,t12,t21,t22,ans,maxx=-1e7,minn=1e7;
int main()
{
    scanf("%s%s",t1,t2);
    for(int z=0;z<1440;z++)
    {
        for(int k=z+1;k<1440;k++)
        {
            int i=z/60,j=k/60,l=z%60,p=k%60;
            t11=i,t12=j,t21=l,t22=p;
            if(t1[0]!='?'&&i/10!=(t1[0]-'0'))continue;
            if(t1[1]!='?'&&i%10!=(t1[1]-'0'))continue;
            if(t2[0]!='?'&&j/10!=(t2[0]-'0'))continue;
            if(t2[1]!='?'&&j%10!=(t2[1]-'0'))continue;
            if(t1[3]!='?'&&l/10!=(t1[3]-'0'))continue;
            if(t1[4]!='?'&&l%10!=(t1[4]-'0'))continue;
            if(t2[3]!='?'&&p/10!=(t2[3]-'0'))continue;
            if(t2[4]!='?'&&p%10!=(t2[4]-'0'))continue;
            ans=abs((t12*60+t22)-(t11*60+t21));
            maxx=max(maxx,ans);
            minn=min(minn,ans);
        }
    }
    printf("%d %d",minn,maxx);
    return 0;
}

H.字母收集

常规dp,状态转移方程如下:

score[i][j]=max(score[i][j]+score[i-1][j],score[i][j]+score[i][j-1]);

上代码:

#include<iostream>
using namespace std;
char map[505][505];
int score[505][505];
int dx[2]={1,0};
int dy[2]={0,1};
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    getchar();
    for(int i=1;i<=n;i++)
    { 
        for(int j=1;j<=m;j++)
        {
            scanf("%c",&map[i][j]);
        }
        getchar();
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(map[i][j]=='l')score[i][j]=4;
            else if(map[i][j]=='o')score[i][j]=3;
            else if(map[i][j]=='v')score[i][j]=2;
            else if(map[i][j]=='e')score[i][j]=1;
        }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {    
            score[i][j]=max(score[i][j]+score[i-1][j],score[i][j]+score[i][j-1]);
            ans=max(ans,score[i][j]);
        }
    }
    printf("%d",ans);
    return 0;
}

I.数字染色

思路是容斥加莫比乌斯,作为学习方向涵盖数论的蒟蒻,我竟然还没有学,学了就补,题解可能会迟到,但是不会咕咕咕.


后续来了:

浅谈莫比乌斯函数&容斥定理(例题:数字染色,完全平方数)_ccsu_yuyuzi的博客-CSDN博客

补题了,大家可以从这里来看看.

J.小红的心愿

签到

K.小红的树

开两个数组,一个为fa数组,记录下标i的父亲节点,一个为son,记录下标i节点的子树被染成红色的数量.然后遍历染色字符串,当被染成红色就从这个点往上知道根节点1的所有节点的子树染色数量son[i]++.

#include<iostream>
#include<cstring>
using namespace std;
int fa[100005];
int son[100005];
char color[100005];
void col(int i)
{
    while(i!=1)
    {
        i=fa[i];
        son[i]++;
    }
    return ;
}
int main()
{
    memset(son,0,sizeof son);
    int q,n,t;
    scanf("%d",&n);
    fa[1]=1;
    for(int i=2;i<=n;i++)
    {
        scanf("%d",&t);
        fa[i]=t;
    }
    scanf("%s",color);
    for(int i=0;i<n;i++)
    {
        if(color[i]=='R')
        {
            son[i+1]++;
            col(i+1);
        }
    }
    scanf("%d",&q);
    while(q--)
    {
        scanf("%d",&t);
        printf("%d\n",son[t]);
    }
    return 0;
}

L.重排字符串

该题排发有那么多种,我采用的排法是先记录各个字母的个数,然后按照次数多少排序,从大到小,然后就把这些字母先从头往字符串的奇数位置放,放满了再从头开始往偶数位置放.(设len为字符串长度)

这个策略成功的原因,就是首先确定,当出现的最多次的字母(出现次数若为奇数,最大为len/2+1,若为偶数,虽大为len/2)我们先把他摆放在字符串中,无论出现次数是奇数还是偶数,最多我们就是把它们放到字符串结尾的位置,这样它们肯定不会因为相邻的相同而不符合规则,而之后在放的其他字符也会因为有字母在中间隔着而不会产生相邻的相同的情况.

上代码:

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
string str;
struct node
{
    int id;
    int time;
}vis[30];
bool cmp(node a,node b)
{
    return a.time>b.time;
}
char s[100005];
int main()
{
    int len,maxx=0;
    cin>>len;
    cin>>str;
    for(int i=0;i<26;i++)
    {
        vis[i].id=i;
        vis[i].time=0;
    }
    for(int i=0;i<len;i++)
    {
        vis[str[i]-'a'].time++;
    }
    sort(vis,vis+26,cmp);
    int stop=0;
    for(int i=0;i<26;i++)
        if(vis[i].time==0)
        {
            stop=i;
            break;
        }
    int idx=0;
    maxx=vis[0].time;
    if((len%2==0&&maxx>len/2)||(len%2!=0&&maxx>(len/2+1)))
    {
        cout<<"no"<<"\n";
    }
    else 
    {
        cout<<"yes"<<"\n";
        for(int i=0;i<len;i+=2)
        {
            if(vis[idx].time!=0&&idx<26)
            {
                vis[idx].time--;
                s[i]=(char)(vis[idx].id+'a');
            }
            if(vis[idx].time==0)idx++;
        }
        for(int i=1;i<len;i+=2)
        {
            if(vis[idx].time!=0&&idx<26)
            {
                vis[idx].time--;
                s[i]=(char)(vis[idx].id+'a');
            }
            if(vis[idx].time==0)idx++;
        }
        printf("%s",s);
    }
    return 0;
}

以上就是本场比赛题解.本蒟蒻要是有什么地方写错了,还望大佬斧正.

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值