【做题笔记】 P5051 【[COCI2017-2018#7] Timovi】

本题所有程序默认开启O2,否则超时。这不是正解,但是比正解简洁明了,代码可读性强,但是不开O2超时。如有能力,建议学习正解。

另:题面有一个地方不太清楚:当满足 \(m<k\) 时,若仍有剩余,要放到下一个队伍里去。


根据题意,我们不难想到,设 \(ans_i\) 表示第 \(i\) 个队伍有多少人,那么,我们就不难想到一直放下去,知道满足条件为止。

但是注意到当到达第 \(n\) 个队伍的时候要逆序(i.e. \(n->(n-1)->...->2->1->2->...->(n-1)->n...\))。所以我们记一个flag变量,当flag=0的时候,说明是正序,否则倒序。然后设 \(now\) 表示现在到了第 \(num\) 个队伍,若 \(now=n\) 则另flag=1,若 \(now=1\) 则令flag=0

需要注意在flag变动时看一下当前 \(m\) 是否小于 \(k\)

参考代码(91points):

#include <cstdio>

int now=1, n, k, m, ans[200010], flag;

inline int read()
{
    int s=0,w=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') s=s*10+ch-'0' , ch=getchar();
    return s*w;
}

int main(void) {
    n=read(), k=read(), m=read();

    while(1) {
        if(now==n) {
            flag=1; //标记从现在开始就是倒序
            ans[now] += k; //加上
            m -= k; //减去
            now--; //倒序则不断把now减一,模拟了倒序的过程
            if(m<k) { //判断是否满足条件
                ans[now] += m;
                break; 
            }
            continue; //不用继续进行后面的操作了
        }

        else if(now==1) {
            flag=0;
            ans[now] += k;
            m -= k;
            now++;
            if(m<k) {
                ans[now] += m;
                break; 
            }
            continue;
        } //同上

        if(m<k) { //这里仍然要判断
            ans[now] += m;
            break; 
        }

        ans[now] += k;
        m -= k;

        if(flag) --now; //如果当前标记为倒序,则now--
        else ++now; //否则now++

    }

    //printf("QAQ\n");  //QAQ

    for(int i=1;i<=n;i++)
        printf("%d ", ans[i]);

    return 0;
}

91分,不是因为TLE,而是因为WA!为啥呢?

上面那份代码的hack数据(感谢@metaphysis 的提供):

3 2 4

这份数据正确的结果应该是2 2 0,但是上面那份程序跑出了2 0 2

为什么会出现这样的情况呢?原因出在判断 \(now\) 那里,以第一个判断为例:

if(now==n) {
    flag=1; 
    ans[now] += k;
    m -= k; 
    now--;
    if(m<k) {
        ans[now] += m;
        break; 
    }
    continue;
}

假如说正好分配完,没有多余,那么上面那份代码就出现了一个致命错误。为了帮助理解,我们演示一遍(人脑模拟gdb 233):

扫描二维码关注公众号,回复: 10396982 查看本文章

现在now=n, k=2, m=0
flag=1(没啥好说的)
ans[now] += k(看到问题了么?明明已经没有人了,但是还是加了一个不存在的 \(k\)
m-=k (问题严重了!现在 \(m=-2\) !)
now-- (导火索,现在去前面一个队伍了)
if(m<k) { (进入)
ans[now] += m (注意,这里是把前面一个队伍加上一个人(相当于减去))
break; (跳出while循环)

我们发现,我们把 \(ans_n\) 平白无故地加上了两个人;而把 \(ans_{n-1}\) 平白无故地减去了两个人(总人数虽然没变,但是答案已经错了)。

如果您可以明白上面那套东西,解决办法也就很自然了:提前判断是否满足条件,就可以解决这个问题。

最终AC代码(再次提醒开了O2

#include <cstdio>

int now=1, n, k, m, ans[2000010], flag;

inline int read()
{
    int s=0,w=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') s=s*10+ch-'0' , ch=getchar();
    return s*w;
}

int main(void) {
    n=read(), k=read(), m=read();

    while(1) {
        if(now==n) {

            if(m<k) {
                ans[now] += m;
                //printf("Now we need break and now The 'm'=%d\n", m);
                break; 
            } //提前判断即可。

            flag=1;
            ans[now] += k;
            //("now=%d,OK! a[%d]=%d\n", n, n, ans[n]);
            m -= k;
            now--;

            continue;
        }

        else if(now==1) {

            if(m<k) {
                ans[now] += m;
                //printf("Now we need break and now The 'm'=%d\n", m);
                break; 
            }

            flag=0;
            ans[now] += k;
            //printf("now=1,OK! a[1]=%d\n", ans[1]);
            m -= k;
            now++;

            continue;
        }

        if(m<k) {
            ans[now] += m;
            //printf("Now we need break and now The 'm'=%d\n", m);
            break; 
        }

        ans[now] += k;
        m -= k; 
        //printf("now=%d,OK! a[%d]=%d\n", now, now, ans[now]);
        if(flag) --now;
        else ++now;
    }

    //printf("QAQ\n");

    for(int i=1;i<=n;i++)
        printf("%d ", ans[i]);

    return 0;
}

猜你喜欢

转载自www.cnblogs.com/BlueInRed/p/12617595.html