NOIP2017 普及组 蒟蒻的题解报告

前言

这是本蒟蒻在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
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
这时的 10k 1000
23233 % 1000 正好是 233 ,取到了最后三位

代码如下:

#include
#include
#include
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
#include
#include
#include
#include
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
#include
#include
#include
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
写的不好大佬轻喷

你可能感兴趣的:(noip)