1. 程式人生 > >NOIP2017普及組 解題報告

NOIP2017普及組 解題報告

前言

好吧,由於賽時本人還是一個蒟蒻,(雖然現在也是),導致一直沒有做完後面兩題。。。
先說下賽時分數吧。。。
前兩題日常水過,後兩題日常放棄,其實第三題有想著做但是沒時間了

第一題 第二題 第三題 第四題
Accepted Accepted WA WA

。。。

解題報告

第一題 成績

連結

大意

輸入a,b,c計算a0.2+b0.3+c0.5

思路

模擬,由於C++的強制轉換很騷,所以就要注意一下
時間複雜度和空間複雜度都是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 }

第二題 圖書管理員

連結

大意

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

思路

重點在%,模擬,本人程式碼奇醜無比,奇low無比(當時是真的蒟蒻)
時間複雜度:O(nlogn+m(nleni+len))
空間複雜度:O(2n+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 }

終於醜完了

第三題 棋盤

連結

大意

有一個n×n的棋盤,有m個色塊是有色的。
有色的色塊間有兩種規則

  1. 若兩個格子顏色相同,則這兩個格子之間行走的代價為0
  2. 若兩個各自顏色不同,則這兩個格子之間行走的代價為1
    有色和無色或無色和有色間有一種規則

可以施展膜拜大法(魔法),將無色的方格變成有色的方格的顏色,代價為2

現求從左上角走到右下角的最小花費,若不能到達,輸出-1

思路

暴搜會超時,解法很多,這裡列舉一下

  1. bfs
  2. dfs+剪枝
  3. dp
  4. 最短路(這個很複雜,洛谷上有詳細介紹,這裡主要講dfs)

    其實dfs很容易理解,就是開一個use表示是否有使用魔法,也可以直接在dfs裡面加一個flag來判斷,效果是一樣的
    值得注意的是,走過的路還能再走,所以不用判斷是否已經走過,而是儲存最優解
    時間複雜度:O(n2)(應該是吧)
    空間複雜度:O(3n2)(若使用flag作為一個引數,空間複雜度可一將為O(2n2)

程式碼

終於不是奇醜無比了,當然在dalao面前永遠是醜的。。。

// 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);//輸出
}

第四題 跳房子

連結

大意

在一根數軸上有n個點,給出它們距離原點的距離以及它們的價值,現在有一個機器人,他每次可以向右邊走d個單位長度,但是為了的得到一定的價值k,需要改進這個機器人。已知花費g點金幣可以使它能跳的距離變成
max(1,dg)...g+d之間,問至少需要多少金幣可以拿到k點價值

思路

首先可以想到若使用g枚金幣可以,那麼使用g+1枚也必然可以,所以這就滿足二分的條件
至於如何確定二分的答案是否正確呢?需要用到動態規劃,先給出方程

dp[i]=max(dp[i],dp[j]+a[i])
當然,這個轉移是一定要滿足可以跳到這個位置的情況下的,時間複雜度為O(logMaxn×n2)會超時
所以要用到單調佇列優化
先簡單提一下單調佇列,例如現在此佇列為
9 8 7 6 5 2 1
現在我要插入7,則單調佇列變成
9 8 7 而不是 9 8 7 6 5 2 1 7
時間複雜度:O(logMaxn×n)
空間複雜度:O(3n)

程式碼(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-