NOIP2017普及组 解题报告

前言

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

第一题 第二题 第三题 第四题
Accepted Accepted WA WA

。。。

解题报告

第一题 成绩

链接

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
#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
#include
#include
#include
#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 xbool 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 u s e 表示是否有使用魔法,也可以直接在 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
#include
#include
#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 d 个单位长度,但是为了的得到一定的价值 k k ,需要改进这个机器人。已知花费 g g 点金币可以使它能跳的距离变成
max(1,dg)...g+d m a x ( 1 , d − g ) . . . g + d 之间,问至少需要多少金币可以拿到 k k 点价值

思路

首先可以想到若使用 g 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
#include
#include
#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
#include
#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);//输出
}

你可能感兴趣的:(解题报告,STL,dp,模拟,二分)