Solution [NOIP普及组2013] 小朋友的数字
题目大意:求\(max\{f[i]\}\),其中\(f[i]= max\{f[j]+ d[j] \;| \;j < i\}\)d[j]$表示区间[1,j]内的最大连续子段和
分析:看了题目题目大意,这题显然还是挺水的吧.
我们先看状态\(d\)怎么求:
我们设\(td[i]\)表示以\(i\)结尾的最大连续子段和,那么显然
\(td[i] = max(td[i - 1] + val[i],val[i])\),\(val[i]\)即第\(i\)个小朋友每人手上的数字
然鹅我们要求的是\([1,j]\)内的最大连续子段和
所以\(d[i] = max(d[i - 1],td[i])\)这个显然还是很容易想到吧?
解决了状态\(d\)以后,我们再来看状态\(f\)如何求.这题的状态显然是\(O(n)\)的,可是如果\(O(n)\)转移的话,复杂度会炸成\(O(n ^ 2)\),无法接受.那么快速转移有几种方法:
- 数据结构,如线段树等(
我可能数据结构学傻了,线段树打到一半发现做复杂了),可以做到\(O(logn)\)转移,总复杂度\(O(nlogn)\) - 临时变量 我们可以用一个临时变量\(temp\_f\)来表示\(max\{f[j] + d[j]\; | \; 1 \leq j < i\}\),然后直接\(f[i] = temp\_f\),\(temp\_f = max(temp\_f,f[i] + d[i])\),可以做到\(O(1)\)转移,总复杂度\(O(n)\)
然后我们发现,状态\(td\),\(d\),\(f\)之间互不依赖,我们完全可以一个\(for\)循环来进行状态的转移.并且第\(i\)个状态要么依赖第\(i - 1\)个状态,要么依赖区间\([1,i - 1]\)内的极值,我们完全可以用临时变量将这些状态全部保存下来,然后程序就跑的飞快
另外此题有坑点,会爆\(long\;long\)
解决方案:
- 手写高精度 但是这涉及到高精度取余,不好写
- 两个\(long \; long\)手动模拟\(128\)位整数 这个就是位运算版的高精了
- **__int128** 这个很方便,只是需要自己手写读入输出优化
#include <cstdio>
#include <algorithm>
#include <cctype>
using namespace std;
typedef __int128 type;//我也是迫不得已
const type INF = 0x7fffffffffffffff;//就用long long的极值吧
template <typename T>
inline void read(T &x){//读入优化,注意有负数
x = 0;char c = getchar();int f = 1;
while(!isdigit(c))f = (c == '-') ? (-1) : f,c = getchar();
while(isdigit(c))x = x * 10 + c - '0',c = getchar();
x *= f;
}
char buf[32];
template <typename T>
inline void write(T x){//输出优化
int p = 0;
do{
buf[++p] = x % 10;
x /= 10;
}while(x);
for(;p;p--)
putchar(buf[p] + '0');
}
type temp_d,d,temp_f,f,x,mod,ans = -INF;//几个临时变量,具体后面回收
int n;
int main(){
#ifdef LOCAL
freopen("fafa.in","r",stdin);//强烈建议使用宏编译,本机调试超级方便
#endif
read(n),read(mod);
read(x);//第一个小朋友很特殊
temp_d = d = x;//temp_d表示以i结尾的最大连续子段和,d表示区间[1,i]内的最大连续子段和
f = d;//由题意可知第一个小朋友的分数
temp_f = f + d;//temp_f表示区间[1,i - 1]内最大的f[j] + d[j]
ans = max(ans,f);//更新答案
for(int i = 2;i <= n;i++){
read(x);//读入
f = temp_f;//进行f的转移
ans = max(ans,temp_f);//更新答案
temp_d = max(temp_d + x,x);//求以i结尾的最大连续子段和
d = max(d,temp_d)//更新[1,i]内的最大连续子段和
temp_f = max(temp_f,f + d);//更新临时变量
}
if(ans < 0)putchar('-'),ans = -ans;//特判负数
write(ans % mod);putchar('\n');//输出
return 0;
}
这份代码运行时间还可以进一步缩短,\(fread\)读入优化蒟蒻懒得写了,各位有兴趣可以冲一下最优解