NOIP2017普及组 解题报告

107 篇文章 1 订阅
62 篇文章 0 订阅

前言

好吧,由于赛时本人还是一个蒟蒻,(虽然现在也是),导致一直没有做完后面两题。。。
先说下赛时分数吧。。。
前两题日常水过,后两题日常放弃,其实第三题有想着做但是没时间了

第一题第二题第三题第四题
AcceptedAcceptedWAWA

。。。

解题报告

第一题 成绩

链接

https://www.luogu.org/recordnew/lists?uid=52915&pid=3954

大意

输入 a,b,c a , b , c 计算 a0.2+b0.3+c0.5 a ∗ 0.2 + b ∗ 0.3 + c ∗ 0.5

思路

模拟,由于 C++ C + + 的强制转换很骚,所以就要注意一下
时间复杂度和空间复杂度都是 O(1) O ( 1 )

代码

#include<cstdio>
#define sr c=getchar()
#define input read()
#define pd (c<'0'||c>'9')
using namespace std;
int read()//楼楼超丑的代码,莫介意
{
    int d=1,f=0;char c;
    while (sr,pd) if (c=='-') d=-1;f=f*10+c-48;
    while (sr,!pd) f=f*10+c-48;
    return d*f;
}
int main()
{
    int x,y,z;
    x=input;y=input;z=input;
    int a,b,c;
    a=x*20;b=y*30;c=z*50;
    int ans=(a+b+c)/100;
    printf("%d",ans);//防止强制转换出现的bug
}

第二题 图书管理员

链接

https://www.luogu.org/recordnew/lists?uid=52915&pid=3955

大意

忘了(真的忘了。。。,真的不是我懒

思路

重点在%,模拟,本人代码奇丑无比,奇low无比(当时是真的蒟蒻)
时间复杂度: O(nlogn+m(nleni+len)) O ( n l o g n + m ( n l e n i + ∑ l e n ) )
空间复杂度: O(2n+m) O ( 2 n + m )
(丑到怀疑人生)

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define sr c=getchar()
#define input read()
#define pd (c<'0'||c>'9')
using namespace std;
int n,m;
int book[1011]; int z[1011];
int numb[111];//一群丑数组
int len;
bool ok;
bool cmp(int x,int y)//一个丑排序
{
    return x<y;
}
bool pds(int x)//一个丑判断
{

    int j=0;
    while (x>0)
    {
        j++;
        z[j]=x%10;
        x/=10;
        //printf("%d",z[j]);
    }
    int i=0;
    if (len>j) return false;
    for (int k=len;k>0;k--)
    {
        i++;
        if (z[i]!=numb[k]) return false;
    }
    return true;
}
int read()//一个丑输入流
{
    char c;int d=1,f=0;
    while (sr,pd) if (c=='-') d=-1;f=f*10+c-48;
    while (sr,!pd) f=f*10+c-48;
    return d*f;
}
int main()
{
    n=input;m=input;
    for (int i=1;i<=n;i++)
     book[i]=input;//一个丑输入
    sort(book+1,book+1+n,cmp);//丑排序
    for (int i=1;i<=m;i++)
    {
     len=input;char lc;//memset(numb,0,sizeof(numb));
     for (int j=1;j<=len;j++) 
     {
        lc=getchar();
        numb[j]=lc-48;
     }ok=false;//丑输入
     for (int j=1;j<=n;j++)
     {
      if (pds(book[j])) {ok=true;printf("%d\n",book[j]);break;}//丑查找
     }
     if (!ok) printf("-1\n");//丑判断
    }
    return 0;//丑return
}

终于丑完了

第三题 棋盘

链接

https://www.luogu.org/recordnew/lists?uid=52915&pid=3956

大意

有一个 n×n n × n 的棋盘,有 m m 个色块是有色的。
有色的色块间有两种规则

  1. 若两个格子颜色相同,则这两个格子之间行走的代价为0
  2. 若两个各自颜色不同,则这两个格子之间行走的代价为1
    有色和无色或无色和有色间有一种规则

可以施展膜拜大法(魔法),将无色的方格变成有色的方格的颜色,代价为2

现求从左上角走到右下角的最小花费,若不能到达,输出-1

思路

暴搜会超时,解法很多,这里列举一下

  1. bfs
  2. dfs+剪枝
  3. dp
  4. 最短路(这个很复杂,洛谷上有详细介绍,这里主要讲dfs)

    其实dfs很容易理解,就是开一个use表示是否有使用魔法,也可以直接在 dfs d f s 里面加一个 flag f l a g 来判断,效果是一样的
    值得注意的是,走过的路还能再走,所以不用判断是否已经走过,而是保存最优解
    时间复杂度: O(n2) O ( n 2 ) (应该是吧)
    空间复杂度: O(3n2) O ( 3 n 2 ) (若使用flag作为一个参数,空间复杂度可一将为 O(2n2) O ( 2 n 2 )

    代码

    终于不是奇丑无比了,当然在 dalao d a l a o 面前永远是丑的。。。

    // luogu-judger-enable-o2
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #define LL long long
    #define r(i,a,b) for(int i=a;i<=b;i++)
    #define check(x,y) (x>=1&&x<=n&&y>=1&&y<=n)//判断是否在期盼内
    using namespace std;int n,m,ans=536870912;
    const short dx[4]={-1,0,1,0};
    const short dy[4]={0,1,0,-1};//四个方向
    bool use[101][101];int color[101][101],f[101][101];//use为是否使用魔法,color表示此点的颜色,f表示最优解
    LL read()//输入流
    {
        char c;int f=0,d=1;
        while((c=getchar())<48||c>57)if(c=='-')d=-1;f=(f<<3)+(f<<1)+c-48;
        while((c=getchar())>=48&&c<=57)f=(f<<3)+(f<<1)+c-48;
        return d*f;
    }
    void dfs(int x,int y,int now)
    {
        if(!check(x,y)||now>=f[x][y]) return;//超出范围或者不是最优则退出
        if(x==n&&y==n) {ans=min(now,ans);return;}f[x][y]=now;//保存
        r(i,0,3)
        {
            int qx=x+dx[i],qy=y+dy[i];//获取位置
            if(check(qx,qy))//判断
             {
                if(use[x][y]&&!color[qx][qy]) continue;//若已经使用魔法但是目标格子是空的那么直接返回
                if(color[qx][qy])
                 if(color[x][y]==color[qx][qy]) dfs(qx,qy,now);//颜色相同
                  else dfs(qx,qy,now+1);//不相同
                else
                 if(!use[x][y]&&!color[qx][qy])//使用魔法
                  {
                    use[qx][qy]=true;
                    color[qx][qy]=color[x][y];//标记已经使用并且改变颜色
                    dfs(qx,qy,now+2);
                    use[qx][qy]=false;
                    color[qx][qy]=0;//回溯
                  }
             }
        }
    }
    int main()
    {
        memset(f,127/3,sizeof(f));
        n=read();m=read();
        r(i,1,m) color[read()][read()]=read()+1;//输入,为了更好区分,所以每个格子都+1,这样区分开了红色和白色的格子
        dfs(1,1,0);//搜索
        if(ans==536870912) puts("-1");else printf("%d",ans);//输出
    }

    第四题 跳房子

    链接

    https://www.luogu.org/problemnew/show/P3957

    大意

    在一根数轴上有 n n 个点,给出它们距离原点的距离以及它们的价值,现在有一个机器人,他每次可以向右边走d个单位长度,但是为了的得到一定的价值 k k ,需要改进这个机器人。已知花费g点金币可以使它能跳的距离变成
    max(1,dg)...g+d m a x ( 1 , d − g ) . . . g + d 之间,问至少需要多少金币可以拿到 k k 点价值

    思路

    首先可以想到若使用g枚金币可以,那么使用 g+1 g + 1 枚也必然可以,所以这就满足二分的条件
    至于如何确定二分的答案是否正确呢?需要用到动态规划,先给出方程

    dp[i]=max(dp[i],dp[j]+a[i]) d p [ i ] = m a x ( d p [ i ] , d p [ j ] + a [ i ] )

    当然,这个转移是一定要满足可以跳到这个位置的情况下的,时间复杂度为 O(logMaxn×n2) O ( l o g M a x n × n 2 ) 会超时
    所以要用到单调队列优化
    先简单提一下单调队列,例如现在此队列为
    9 8 7 6 5 2 1
    现在我要插入7,则单调队列变成
    9 8 7 而不是 9 8 7 6 5 2 1 7
    时间复杂度: O(logMaxn×n) O ( l o g M a x n × n )
    空间复杂度: O(3n) O ( 3 n )

    代码(STL)

    #include<cstdio>
    #include<algorithm>
    #include<deque>
    #define LL long long
    #define r(i,a,b) for(int i=a;i<=b;i++)
    #define N 500001
    using namespace std;LL n,d,k,l,r,mid,far[N],num[N],ans=-1,dp[N];
    LL read()
    {
        char c;int f=0,d=1;
        while((c=getchar())<48||c>57)if(c=='-')d=-1;f=(f<<3)+(f<<1)+c-48;
        while((c=getchar())>=48&&c<=57)f=(f<<3)+(f<<1)+c-48;
        return d*f;
    }
    bool check(int money)
    {
        deque<int>q;
        far[0]=0;
        fill(dp,dp+n,0);//初始化
        int high=money+d,low=max(d-money,(LL)1);//记得末尾是1
        int j=0;
        r(i,1,n)
        {
            while(far[i]-far[j]>=low)
            {
                while(!q.empty()&&dp[j]>=dp[q.back()]) q.pop_back();//弹出去
                q.push_back(j++);//放进来
            }
            while(!q.empty()&&far[i]-far[q.front()]>high) q.pop_front();//弹出去
            if(q.empty())dp[i]=-9999999999;//空了
            else dp[i]=dp[q.front()]+num[i];//没空
            if(dp[i]>=k) return 1;//判断
        }
        return 0;
    }
    int main()
    {
        n=read();d=read();k=read();
        r(i,1,n)
         far[i]=read(),num[i]=read();
        int l=1,r=N<<5;//范围
        while(l<=r)
        {
            mid=(l+r)>>1;
            if(check(mid)) ans=mid,r=mid-1;else l=mid+1;//二分
        }
        printf("%lld",ans);
    }

    代码(单调队列)

    #include<cstdio>
    #include<algorithm>
    #define LL long long
    #define r(i,a,b) for(int i=a;i<=b;i++)
    #define N 500001
    using namespace std;LL n,d,k,l,r,mid,far[N],num[N],ans=-1,dp[N];
    LL read()
    {
        char c;int f=0,d=1;
        while((c=getchar())<48||c>57)if(c=='-')d=-1;f=(f<<3)+(f<<1)+c-48;
        while((c=getchar())>=48&&c<=57)f=(f<<3)+(f<<1)+c-48;
        return d*f;
    }
    bool check(int money)//方法和上面是一样的
    {
        int q[N]={0};
        fill(dp,dp+n,-1);dp[0]=0;
        int high=money+d,low=max(d-money,(LL)1);
        int j=0,head=1,tail=0;
        r(i,1,n)
        {
            while(far[i]-far[j]>=low)
            { 
                while(head<=tail&&dp[j]>=dp[q[tail]]) tail--;
                q[++tail]=j++;
            }
            while(head<=tail&&far[i]-far[q[head]]>high) head++;
            if(head<=tail)dp[i]=dp[q[head]]+num[i];else dp[i]=-9999999999;
            if(dp[i]>=k) return 1;
        }
        return 0;
    }
    int main()
    {
        n=read();d=read();k=read();
        r(i,1,n)
         far[i]=read(),num[i]=read();
        int l=1,r=far[n];
        while(l<=r)
        {
            mid=(l+r)>>1;
            if(check(mid)) ans=mid,r=mid-1;else l=mid+1;//二分
        }
        printf("%lld",ans);//输出
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值