1. 程式人生 > >[NOIP2013][vijos1850]小朋友的數字(dp+貪心)

[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<iostream>
#include<cstring>
#include<cstdio>
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一定要避免犯不該犯的錯誤,想清楚狀態。
②資料範圍一定不要算錯了,不要想當然。