[CF1421E]Swedish Heroes

题目

传送门 to CF

思路

显然是找性质,不可能区间 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+2y1(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+2y1(mod3) ,那么这个序列 + + + 开头、以 + + + 结尾。毕竟它是 “黑白相间” 的,如果 − - 开头是无法构造出的。

为什么就充分了呢?考虑在此种情况下归纳构造出一个合法的解。也就是还原出最后一步是哪两个集合体的操作。也就是区间 d p \tt dp dp 中的前驱。

首先说明,不可能还原出特殊情况,即不存在相邻的相同符号的情况。假如你能生成 + − + − + +-+-+ +++ ,那么它在上一轮中是 − + − + − -+-+- ++(并且是后缀或前缀),那么你就直接将末端的 − - 作为最后一次被合并的。然后就剩下 − + − + -+-+ ++ (或者 + − + − +-+- ++ ,都一样),继续操作,把它删干净。剩下的序列中仍然含有原来的相邻相同符号,仍然合法。

会不会把相邻的相同符号删掉呢?不会。假如把它删掉了,那就留下一个不删 😐

假如两端都是 − - ,则何如?答曰:随意选择一个作为最后被操作的。假如出现特殊情况就反悔就好了。两边都会出现特殊情况?不可能。出现特殊情况需要 − − + − + − + − --+-+-+- +++ ,右端一定不会是特殊情况了。

假如两端至少有一个 + + + ,那么将这一端设置为前端,另一端为后端。从前往后找到第一次两个相邻的相同符号。容易发现此时左边满足 x + 2 y ≡ 1 x+2y\equiv 1 x+2y1 的规律,因为左边只可能形如

+ − + − + − + + ( 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+2y1(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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值