题目
思路
显然是找性质,不可能区间 d p \tt dp dp 的。
结论是,若 “正号” 数量为 x x x ,“负号” 数量为 y y y ,则 x + 2 y ≡ 1 ( m o d 3 ) x+2y\equiv 1\pmod{3} x+2y≡1(mod3)
证明方法很多,比如归纳法。对于一个数字显然满足(只有一个正号);当两个满足这样性质的东西合并时呢?显然两边都会反号。于是原来的 x + 2 y x+2y x+2y 变成了 y + 2 x y+2x y+2x ,不难发现二者是相反数。
所以新的 x + 2 y x+2y x+2y 为 ( − 1 ) + ( − 1 ) ≡ 1 ( m o d 3 ) (-1)+(-1)\equiv 1\pmod{3} (−1)+(−1)≡1(mod3) 。于是 必要条件 得到。
发现一个特殊情况, + − + − + +-+-+ +−+−+ 就并不合法。该死!所以我们修正一下,得到的就是 充要条件。加入一个新的判断:原序列存在 相邻的两个相同符号。这是因为第一次操作的两个数必然同号。
此时要再说明一点:如果原序列不存在两个相邻的相同符号,但是 x + 2 y ≡ 1 ( m o d 3 ) x+2y\equiv 1\pmod 3 x+2y≡1(mod3) ,那么这个序列 以 + + + 开头、以 + + + 结尾。毕竟它是 “黑白相间” 的,如果 − - − 开头是无法构造出的。
为什么就充分了呢?考虑在此种情况下归纳构造出一个合法的解。也就是还原出最后一步是哪两个集合体的操作。也就是区间 d p \tt dp dp 中的前驱。
首先说明,不可能还原出特殊情况,即不存在相邻的相同符号的情况。假如你能生成 + − + − + +-+-+ +−+−+ ,那么它在上一轮中是 − + − + − -+-+- −+−+−(并且是后缀或前缀),那么你就直接将末端的 − - − 作为最后一次被合并的。然后就剩下 − + − + -+-+ −+−+ (或者 + − + − +-+- +−+− ,都一样),继续操作,把它删干净。剩下的序列中仍然含有原来的相邻相同符号,仍然合法。
会不会把相邻的相同符号删掉呢?不会。假如把它删掉了,那就留下一个不删 😐
假如两端都是 − - − ,则何如?答曰:随意选择一个作为最后被操作的。假如出现特殊情况就反悔就好了。两边都会出现特殊情况?不可能。出现特殊情况需要 − − + − + − + − --+-+-+- −−+−+−+− ,右端一定不会是特殊情况了。
假如两端至少有一个 + + + ,那么将这一端设置为前端,另一端为后端。从前往后找到第一次两个相邻的相同符号。容易发现此时左边满足 x + 2 y ≡ 1 x+2y\equiv 1 x+2y≡1 的规律,因为左边只可能形如
+ − + − + − + + ( e n d w i t h p l u s ) +-+-+-++\qquad(end\;with\;plus) +−+−+−++(endwithplus)
或者 + − + − + − − ( e n d w i t h m i n u s ) +-+-+--\qquad(end\;with\;minus) +−+−+−−(endwithminus)
将其反号,得到的就是合法策略。由于此时左边满足的是 x + 2 y ≡ − 1 ( m o d 3 ) x+2y\equiv -1\pmod 3 x+2y≡−1(mod3) ,所以 一定不是原串,于是我们找到了令人愉悦的划分点。
然后另一边,根据反悔战略,是不会有特殊情况出现的。那么我们就分出了两个有解的串。最后一步合并二者就行。
所有的证明工作刚刚完成 😮
然后进行
d
p
\tt dp
dp 就比较容易惹。比如
f
(
x
,
y
,
0
/
1
)
f(x,y,0/1)
f(x,y,0/1) 表示前
x
x
x 个数字,模
3
3
3 的余数是
y
y
y ,是否为特殊情况。然后搞定了。话说谁能想得到这个奇怪的结论啊。而且这个又臭又长的充分性证明更烦。
代码
注意特判 n = 1 n=1 n=1 ,因为此时的合法情况是 + + + ,没有相邻的相同符号。
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void getMax(int_ &a,int_ b){
if(a < b) a = b; // become maximum
}
const int MaxN = 200005;
const int_ infty = (1ll<<61)-1;
int_ dp[MaxN][3][2];
int main(){
int n = readint();
if(n == 1){
printf("%d\n",readint());
return 0;
}
for(int i=0; i<=n; ++i)
for(int j=0; j<3; ++j)
dp[i][j][0] = dp[i][j][1] = -infty;
dp[0][0][0] = 0;
for(int i=1,a; i<=n; ++i){
a = readint();
for(int j=0; j<3; ++j){
/* 如果这一位是 + */ ;
getMax(dp[i][(j+1)%3][(i^1)&1],
dp[i-1][j][0]+a);
getMax(dp[i][(j+1)%3][1],
dp[i-1][j][1]+a);
/* 如果这一位是 - */ ;
getMax(dp[i][(j+2)%3][i&1],
dp[i-1][j][0]-a);
getMax(dp[i][(j+2)%3][1],
dp[i-1][j][1]-a);
}
}
printf("%lld\n",dp[n][1][1]);
return 0;
}