NOIP2017 普及组 蒟蒻的题解报告

来源:互联网 发布:维基百科数据库 使用 编辑:程序博客网 时间:2024/06/05 19:23

前言

这是本蒟蒻在CSDN的第一篇题解报告
第一次就写NOIP鸭梨很大
本蒟蒻在PJ中获得了290的成绩,几乎可以说是压线省一(ZJ 分数线280)
所以我也花了一些功夫来研究本次的四道题目
希望明年TG可以获得比较好的成绩(省二什么的)

进入正文——

T1 成绩

原题链接
这道题目从难度上说是一道刚学OI的新手的练习题,难度为0
事实上,还是有一点东西需要注意的,而很多OI选手都不知道
那么我个人在学OI之前是先学的python,用的教材是Sande父子的《父与子的编程之旅》
其中在开头就讲到了关于 精度 的问题
比如下面这段伪代码:

cin >> a >> b >> c;cout << a*0.2+b*0.3+c*0.5 << endl;

如果直接这么写,本机上测试应该不会有任何问题,但是要是放在linux系统下(例如今年的CCF评测机),可能会这样:
输入: 100 100 100
输出: 99.999999997
这也就是为什么今年有那么多人申诉从而迫使CCF改变评测机的设置
本蒟蒻代码如下:

#include<iostream>using namespace std;int a,b,c;int main(){    cin >> a >> b >> c;    a/=10;    b/=10;    c/=10;    a*=2;    b*=3;    c*=5;    cout << a+b+c;    return 0;}

这样写就不会有任何问题,所以本蒟蒻一开始就是100 (省掉了50申诉费)

T2 图书管理员

原题链接
当我从考场里出来时,很多人都说他们用的是string
而用了 取模求最后几位 的只有我和另外一个同学(我们队)
当时就有人有疑问了:形如00005的数怎么办???
我当时也懵了,好在CCF并没有这样的数据
而使用这种算法是建立在一个前提上的:把数的位数告诉你,否则效率不如字符串
而如果把数的位数告诉了你,而数的位数比较大,那么使用这种算法就会比较快(超过longlong的除外)
鉴于 位数k 也输进来了,所以求最后几位就可以直接 % 10k取得

举个栗子:
比如 图书编码为 23233
需求码长度为 3,值为 233
这时的 10k1000
23233 % 1000 正好是 233,取到了最后三位

代码如下:

#include<cstdio>#include<iostream>#include<algorithm>using namespace std;int n,m,q[15]={0,10,100,1000,10000,100000,1000000,10000000,100000000},a[1005];//预处理int main(){    cin >> n >> m;    for(int i=1;i<=n;i++) cin >> a[i];    sort(a+1,a+n+1);//先排序保证每次取得最优解    for(int i=1;i<=m;i++)    {        int x,y;        bool fg=false;        cin >> x >> y;        for(int j=1;j<=n;j++) if(a[j]%q[x]==y)//由于排了序,所以符合条件的第一个必然是最优解        {            fg=true;//记录已取到答案            cout << a[j] << endl;//输出            break;//跳出本层循环即可        }        if(!fg) cout << -1 << endl;//如果没有找到解输出-1    }    return 0;}

T3 棋盘

从这里开始的题目就比较难了,做好准备!
原题链接
我本人在T3打的是dfs暴力
当时获得了60分,如果加一个记搜的话70分
那么这题正解是什么呢?
大致看下来有三种解法:
1. 玄学记搜dfs
2. 靠谱坚实bfs
3. 看不懂的最短路

而 dp 写法是不行的,因为这题有后效性(不明显)
我在一位dalao的帮助下写出了第1种方法,接下来讲讲做法
首先是变量:
核心变量是一个三位数组f,那么f数组表示什么呢?
f[x][y][0] 表示 x,y 有色时的最小花费,
f[x][y][1] 表示要使用魔法把 x,y 变为 颜色0 的最小花费
f[x][y][2] 则表示变为 颜色1
(这不是dp是记搜)
别的应该都比较好理解
代码如下:

#include<cstdio>#include<iostream>#include<algorithm>#include<string>#include<cstring>using namespace std;int m,n,a[105][105],xx,yy,zz,ans=2147400000,f[105][105][3];//前面解释过的f数组,别的都不多说了bool fg;void dfs(int x,int y,int now,int sum,bool mag){    //xy为当前坐标,now为当前格子的颜色,sum为当前花费,mag为是否使用魔法    if(x>m || y>m || x<1 || y<1) return;//如果越界则退出    else if(a[x][y]==0)//如果当前格子没有颜色    {        if(mag) return;//使用过魔法就退出        sum+=2;//否则加上改变的花费        if(f[x][y][1]>sum+now-1)//记搜大法 变相表现走到颜色0的花费        {            f[x][y][1]=sum+now-1;            dfs(x+1,y,1,sum+now-1,1);            dfs(x,y+1,1,sum+now-1,1);            dfs(x-1,y,1,sum+now-1,1);            dfs(x,y-1,1,sum+now-1,1);        }        if(f[x][y][2]>sum-now+2)//同上,表现走到颜色1的花费        {            f[x][y][2]=sum-now+2;            dfs(x+1,y,2,sum-now+2,1);            dfs(x,y+1,2,sum-now+2,1);            dfs(x-1,y,2,sum-now+2,1);            dfs(x,y-1,2,sum-now+2,1);        }    }    else//如果有颜色    {        if(a[x][y]!=now) sum+=1;//如果当前格和上一个不一样就加1花费        if(sum>=f[x][y][0]) return;//记搜大法        f[x][y][0]=sum;//赋值记搜数组        dfs(x+1,y,a[x][y],sum,0);        dfs(x,y+1,a[x][y],sum,0);        dfs(x-1,y,a[x][y],sum,0);        dfs(x,y-1,a[x][y],sum,0);//无脑dfs    }}int main(){    memset(f,0x3f3f3f,sizeof(f));    cin >> m >> n;    for(int i=1;i<=n;i++)    {        cin >> xx >> yy >> zz;        a[xx][yy]=zz+1;        //鉴于有颜色0,为了不赋初值故如此    }    dfs(1,1,a[1][1],0,0);//dfs    if(a[m][m]==0) ans=min(f[m][m][1],f[m][m][2]);    else ans=f[m][m][0];//存答案    if(ans>=10000000) cout << -1;    else cout << ans;//输出    return 0;}

T4 跳房子

原题链接
这道题目难度极高(怎么说也是蓝题)
做法是 dp+二分+单调队列优化
非常烦人!
考试时本蒟蒻因为时间(脑子)不够 写的是枚举+错误的dp
骗到30分(没这30我就GG了)
那么有人问了:单调队列怎么用?
大家都知道,单调队列会维护队首(这题是最大),那么我们就可以将 可以跳到的范围 内的最大值维护在队首,降低大量复杂度
大家可以自己手打一个双向队列(数组模拟)
本蒟蒻就只好用STL大法的deque了
推荐一篇博客:justmeh大佬的单调队列初步
代码可能比较难理解,但是原理解释得很透彻
顺便给道例题:Luogu P1886 滑动窗口
二分和dp就不多说了,大家都看得出来
代码如下:

#include<cstdio>#include<queue>#include<cstring>#include<algorithm>using namespace std;deque <int> q;//STL大法好!!!long long n,d,k,f[500005];long long a[500005],w[500005];//a存位置,w存分数inline int read(){    int sum=0,fu=1;    char ch=getchar();    while(ch<'0' || ch>'9')    {        if(ch=='-') fu=-1;        ch=getchar();    }    while(ch>='0' && ch<='9')    {        sum=(sum << 3)+(sum << 1)+(ch ^ 48);        ch=getchar();    }    return sum*fu;}//读入优化bool DP(int x)//dp本体{    int t1=d-x > 1 ? d-x : 1 ,t2=d+x;//计算最近最远跳跃距离    while(!q.empty()) q.pop_back();//清空队列    memset(f,0,sizeof(f));//清空数组    int t=0;//现在将要入队的点    for(int i=1;i<=n;i++)    {        while(a[i]>=t1+a[t])//如果足够远        {            while(!q.empty() && f[t]>=f[q.back()]) q.pop_back();            q.push_back(t);//进队,维护单调队列            t++;        }        while(!q.empty() && a[i]>a[q.front()]+t2) q.pop_front();        //把太远跳不到的出队        if(q.empty()) f[i]=-9999999999;//如果队列空了,存入负无限        else f[i]=f[q.front()]+w[i];//否则存入当前最大值        if(f[i]>=k) return 1;//如果得到解返回真    }    return 0;//如果一直都没得到解返回假}int main(){    long long ans=9999999999;//最好设这么大,否则会WA    n=read();    d=read();    k=read();    int S=0;    for(int i=1;i<=n;i++)    {        a[i]=read();        w[i]=read();    }    int L=0,R=a[n];    while(L<=R)//二分开始    {        long long mid=L+R >> 1;//mid代表改造花的金币        bool res=DP(mid);//存下是否能够得到k分        if(res)//如果可以        {            ans=min(ans,mid);//存答案            R=mid-1;        }        else L=mid+1;    }    printf("%lld",ans);//输出即可    return 0;}

小结

这次比赛题目 T1、T2 送分,T3、T4对思考要求比较高,本蒟蒻也没有透彻理解,而这次拿到1=也是运气居多,可见我们要走的路还有很长
各位,NOIP2018 TG见。

原创 By Venus
写的不好大佬轻喷