[NOIP2013][vijos1850]小朋友的数字(dp+贪心)

题目描述

传送门

题解

感觉vijos的数据好强啊。在codevs上跑过了然而被卡常数。
其实这道题的题意是很好懂的,但是我发现了两个坑点:
①算特征值的时候的dp,f(i)表示以i结尾的最长连续子序列和,所以最终某个人的特征值F(i)=f(j),1<=j<=i。这个错误非常不应该,以后应该注意。
②很多人想当然或者大概一算觉得答案应该不会超过long long,但是实际上是完全有可能的。极端情况:假设有 106 个小朋友,每一个小朋友的数字都是 109 ,那么他们的特征值就应该为 109,2109,3109106109=1015. 而每个小朋友的分数就应该为 109,2109,4109(1+(1+106)1092)109 ,这很显然已经超过了longlong的范围。其实数据还是比较良心的,实际上不在极限情况下也是很容易爆long long的。
而且还有一个问题是,不能一边做一边取模。因为题目的要求是求出来最大的值然后再取模。如果直接取模的话原先大的值再模意义下有可能变成小的。
那该怎么做呢?
我们可以发现一个非常有用的性质:除了第一个小朋友,所有小朋友的特征值和分数都是不降的。
那么对于某一个小朋友,他的分数只有两种情况
①如果他前一个小朋友的特征值大于0,说明前面所有小朋友的特征值和分数都是一个不降的数列,那么这个小朋友的分数就为他前一个小朋友的分数加上特征值。
①如果他前一个小朋友的特征值小于0,说明前面所有小朋友的特征值是一个负数数列,分数是一个负常数列,那么这个小朋友的分数就为第二个小朋友的分数加上特征值。
然后特判一下第一个小朋友。
虽然这道题是普及组的,但是还是比较有趣的。worth a try.

代码

#include
#include
#include
using namespace std;
#define LL long long
#define N 1000005

const LL inf=1e18;
int n;
LL Mod,Max,a[N],f[N],g[N],h[N],ans;
bool flag;

int main()
{
    scanf("%d%lld",&n,&Mod);
    for (int i=1;i<=n;++i) scanf("%lld",&a[i]);
    f[1]=h[1]=a[1];Max=a[1];
    for (int i=2;i<=n;++i)
    {
        h[i]=max(h[i-1]+a[i],a[i]);
        f[i]=max(f[i-1],h[i]);
    }
    g[1]=f[1];g[2]=f[1]+g[1];
    if (g[2]>=g[1]) flag=true;
    for (int i=3;i<=n;++i)
    {
        if (f[i-1]>0)
        {
            g[i]=g[i-1]+f[i-1];
            if (g[i]>=g[1]) flag=true;
            if (g[i]>inf) g[i]%=Mod;
        }
        else g[i]=g[2];
    }
    if (!flag) ans=g[1]%Mod;
    else ans=g[n]%Mod;
    printf("%lld\n",ans%Mod);
}

总结

①dp一定要避免犯不该犯的错误,想清楚状态。
②数据范围一定不要算错了,不要想当然。

你可能感兴趣的:(题解,dp,贪心,NOIP)