NOIP 2017 普及组解题报告

终于拔掉一棵草,先写点儿想法。

首先网上的写法总是不够清晰。也许他们的清晰,还达不到我需求的。其次,觉得代码能力还需要加强,处理事情的能力还需要加强,要腾出来时间能够去刷代码。第四题消耗了太多的时间,但是没办法,想一想,一道结合二分、动态规划、数据结构的综合题目,能够真的理解下来,花些时间还是划得来的。

单调队列不是不会,恰恰是想过但是没有总结,外出学习是有好处的,希望以后能够自己勤快一些,多多出去学习。学习是难得的跳出自己身边烦乱的环境进入一个自我修行的难得机会,还是很好很好的。

下面是解题报告,留作自我学习的反思。

第一件事,重写这四道题目,顺便建好测评环境。

第一题:

#include <fstream>

using namespace std;

ifstream cin("score.in");
ofstream cout("score.out");

int main(int argc, char** argv)
{
    int a, b, c;
    cin >> a >> b >> c;
    cout << a / 10 * 2 + b / 10 * 3 + c / 10 * 5 << endl;
    return 0;
}


此题很简单,给定范围,a,b,c全都是10的整数倍,最好就不要引入实数的运算。建议大家注意,第一版测评数据,应该是造成了实数引入后,会出问题。大家注意就好。

第二题:

#include <fstream>

using namespace std;

ifstream cin("librarian.in");
ofstream cout("librarian.out");
//返回10的x次方 
int cf(int x)
{
    int s = 1;
    for (int i = 1; i <= x; i++)
        s *= 10;
    return s;
}

int main(int argc, char** argv)
{
    int n, m;
    int a[1005];
    int l, t;
    int c;
    int i, j;
    int z;
    cin >> n >> m;
    for (i = 1; i <= n; i++)
        cin >> a[i];
    for (i = 1; i <= m; i++)
    {
        cin >> l >> t;
        c = cf(l);
        z = -1;
        for (j = 1; j <= n; j++)
            if (a[j] % c == t && (z == -1 || z > a[j]))
                z = a[j];
        cout << z << endl;
    } 
    return 0;
}


最简单的枚举方式。找到最小的,结尾是指定值的方法。因为范围1000还是可以接受的,没有必要用更为高级的方法。

第三题:

很明确的搜索题目,看数据范围,应该估算出深搜是可以完成的。只是有一个重要的点,在白颜色变化颜色的问题上,一定是变化成为跟迈入点的颜色一致即可,没有必要不一致,这个原因很简单下一个如果也一样,那么就花了一次变化的钱,但是不同颜色费没有花,但是如果下一个颜色不一致,则反正下一个也要花颜色不一致的钱,只是先花后花的问题,自然就不用再多此一举的变化为不一致的颜色了。但是因为不能够连续变化,所以也要区分一下是变化的颜色还是不是变化的颜色,所以需要一个是否变化的状态值。

#include <iostream>
#include <cstdlib>
#define INF 99999999

using namespace std;

int g[102][102];
int s[102][102];

int m;

void walk(int y, int x, int money, bool change)
{
    int by[4] = {-1, 0, 1, 0};
    int bx[4] = {0, 1, 0, -1};
    int i, j;
    int y1, x1, color;
    if (g[y][x] == -1 || s[y][x] <= money)
        return ;
    s[y][x] = money;
    for (i = 0; i < 4; i++)
    {
        y1 = y + by[i];
        x1 = x + bx[i];
        if (g[y1][x1] == 0 || g[y1][x1] == 1)
        {
            if (g[y1][x1] == g[y][x])
                walk(y1, x1, money, 0);
            else
                walk(y1, x1, money + 1, 0);
        }
        else
        {
            if (g[y1][x1] == 2 && change == 0)
            {
                g[y1][x1] = g[y][x];
                walk(y1, x1, money + 2, 1);
                g[y1][x1] = 2;
            }
        }
    }
}

int main(int argc, char** argv)
{
    int n;
    int i, j;
    int y, x, t;
    cin >> m >> n;
    for (i = 0; i <= m + 1; i++)
        for (j = 0; j <= m + 1; j++)
            g[i][j] = -1;
    for (i = 1; i <= m; i++)
        for (j = 1; j <= m; j++)
        {
            g[i][j] = 2;
            s[i][j] = INF;
        }    
        
    for (i = 0; i < n; i++)
    {
        cin >> y >> x >> t;
        g[y][x] = t;
    }
    walk(1, 1, 0, 0);//位置y,位置x,消耗经费以及是否更换过颜色
    if (s[m][m] == INF)
        cout << -1 << endl;
    else
        cout << s[m][m] << endl;
    return 0;
}

第四题:

完美100分的知识点有点儿多:二分、DP、数据结构(双向队列)。

二分很容易理解,因为要尽量小,所以,g的值只能二分狂试。

DP也很容易理解,在它允许的范围内,找到可以来的点中DP值最大的点,作为来源。

数据结构(双向队列)主要是为了DP找来源的加速而进行的优化,不适用的话只能过80%,没办法使用的。主要思想是先进入的方向(front),需要剔除掉超过使用范围的点;后进入的方向(back)需要用新进入的点看看前面存活的有没有比自己不优的,直接从后面(back)剔除,因为后进入的存活期更长,也更优,自然要淘汰不优的点。

剩下的问题,就是程序的编写了。主程序是二分,上面DP的过程就是一个check()检测的问题。

#include <cstdlib>
#include <iostream>
#include <queue>

using namespace std;

struct dian
{
    int pos;
    long long val;
    long long dp;
};

deque <dian> q;

dian a[500005];

long long n, d, K;

bool check(int g)
{
    int da, xiao;
    int i, j;
    da = d + g;
    xiao = d - g;
    while (!q.empty())
    {
        q.pop_back();
    }
    a[0].dp = 0;
    a[0].val = 0;
    a[0].pos = 0;
    //0点虽然准备好,但是一定不要进入队列因为它不一定在范围内。
    if (xiao < 0)
        xiao = 0;
    j = 0;//j描述的是未进入的点,因为不一定计算出来即可以使用,所以要等待何时时机进入
    //cout << g << endl;
    for (i = 1; i <= n; i++)
    {
        int l, r;
        //确定能够来源的范围是很方便的操作
        l = a[i].pos - da;
        r = a[i].pos - xiao;
        //此处必须注意,必须先进行更新的操作,再进行超范围后的操作。
        //否则新进去的如果就不在范围内则无法出去会影响结果 
        while (j < i && a[j].pos <= r)
        {
            while (!q.empty() && a[j].dp >= q.back().dp)//将比自己差的点淘汰
                q.pop_back();
            q.push_back(a[j]);//将未使用的点进入
            j++;
        }
        //出必须在更新后面,淘汰已经超过使用期的点
        while (!q.empty() && (q.front().pos < l))
            q.pop_front();
        if (!q.empty())
            a[i].dp = q.front().dp + a[i].val; 
        else   
            a[i].dp = -99999999999LL;
        //cout << "\t" << i << ":" << a[i].dp << endl;
        if (a[i].dp >= K)
            return true;
    }
    return false;
}

int main(int argc, char *argv[])
{
    int i;
    long long ans = -1;
    int l, r, mid;
    cin >> n >> d >> K;
    for (i = 1; i <= n; i++)
    {
        cin >> a[i].pos >> a[i].val;
    }
    l = 0; 
    r = a[n].pos;
    while (l <= r)
    {
        mid = (l + r) / 2;
        if (check(mid))
        {
            ans = mid;
            r = mid - 1;
        }
        else
            l = mid + 1;
    }
    cout << ans << endl;
    return EXIT_SUCCESS;
}

时隔半个多月重新写的版本就是比原先写的版本优化很多。DP数组的使用原来真是很难受。学习,很多时候要合理利用时间来进行遗忘,以检验自己的实现能力。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值