[AGC43B]123 Triangle

280 篇文章 1 订阅

题目

传送门 to AtCoder

思路

无论怎么想,这个题都挺离谱的……官方题解离谱,我的做法更离谱……

官方题解

先来看看这个至少是 l o g i c a l \rm logical logical 的做法。

由于每层都是求差分,可以认为 a i ∈ [ 0 , 2 ] a_i\in[0,2] ai[0,2] 。此时我们发现: 1 1 1 非常特殊,它与 0 , 2 0,2 0,2 相邻时,总是仍然产生 1 1 1

这就是说,只要序列中存在 1 1 1,并且序列不全是 1 1 1,那下一层中也有至少一个 1 1 1 。一直到最后,答案就会是 1 1 1 了。

是否有可能打破这个魔咒?那就是需要全是 1 1 1,那么答案必然是 0 0 0 。知道这个好像还是需要模拟?

还会发现: 0 , 2 0,2 0,2 没有区别。即,统称 0 , 2 0,2 0,2 e e e,那么 1 1 1 e e e 产生 1 1 1 e e e e e e 产生 e e e 1 1 1 1 1 1 产生 e e e 。目的只是检测,是否有全 1 1 1 的时刻存在,或者说最后剩下的是 e e e 还是 1 1 1

这就很简单了,因为明显发现这就是异或运算, e e e 映射到 0 0 0 1 1 1 映射到 1 1 1 。异或运算就是不进位加法,或者说   m o d   \bmod mod 2 2 2 意义下的普通加法,系数就是杨辉三角了。

那么不存在 1 1 1 的时候呢?其实还是异或运算, 2 2 2 映射到 1 1 1 罢了……

众所周知,检查组合数是否为 2 2 2 的倍数,这是 O ( 1 ) \mathcal O(1) O(1) 的位运算,所以总复杂度 O ( n ) \mathcal O(n) O(n)

我的做法

我首先把 n = 4 n=4 n=4 的所有情况打了个表。然后发现了一个规律。——话说为什么我要打 n = 4 n=4 n=4 的表呢?我也不知道。我只是觉得 n = 4 n=4 n=4 包含了 n ⩽ 4 n\leqslant 4 n4 的情况。那么为何不选择 n = 5 n=5 n=5 呢?说不清。——虽说 n = 5 n=5 n=5 的规律也是有的,但是没那么明显。而且就很难往 2 k 2^k 2k 联想了……

  • 1 1 1 存在时:奇数个 1 1 1 则答案为 1 1 1,否则为 0 0 0
  • 1 1 1 不存在时:奇数个 2 2 2 则答案为 2 2 2,否则为 0 0 0

显然我没有给出任何证明,因为这真的是打表发现……只对 n = 4 n=4 n=4 有效。

我觉得这是一个不错的规律。我试着把它再套一层,发现 n = 8 n=8 n=8 也满足该性质!

兴致勃勃的我验证了 n = 12 n=12 n=12 的情况,希望 4 ∣ n 4|n 4n 就有这个规律。结果大失所望。 n = 12 n=12 n=12 就嗝屁了。

可是我没有绝望,接着试下一个, n = 16 n=16 n=16 又行了!敏锐的你一定猜到了: n = 2 k n=2^k n=2k 则满足该条件。

只需要这样就够了。如果 n ≠ 2 k n\ne 2^k n=2k 呢?作二进制分解呗。时间复杂度 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

现在回头来看,这玩意儿的正确性是啥?是 n = 2 k n=2^k n=2k ( n − 1 i )   m o d   2 {n-1\choose i}\bmod 2 (in1)mod2 恒为 1 1 1,即每一位的贡献都是 1 1 1,所以 1 1 1 存在时(它映射到异或运算中的 1 1 1 )看它个数的奇偶性,否则看 2 2 2 个数的奇偶性……

没错,这么一道困难的题目,竟然被愚蠢的暴力打表给破解了!虽然复杂度不是最优。

代码

这里就贴上我的做法的代码吧。官方正解可见于 P P L ′ s    b l o g \rm PPL's\;blog PPLsblog

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
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;
}

const int MaxN = 1000005;
int cnt[MaxN][3], a[MaxN];
void solve(int n){
	if(n == 1) return ;
	int len = 1; // how to compress
	for(; (len<<1)<=n; len<<=1);
	rep(i,1,n){
		memcpy(cnt[i],cnt[i-1],3<<2);
		++ cnt[i][a[i]];
	}
	rep(i,1,n-len+1){
		int one = cnt[i+len-1][1]-cnt[i-1][1];
		int two = cnt[i+len-1][2]-cnt[i-1][2];
		if(one) a[i] = (one&1);
		else a[i] = (two&1)<<1;
	}
	return solve(n-len+1);
}

char str[MaxN];
int main(){
	int n = readint();
	scanf("%s",str+1);
	rep(i,1,n) a[i] = str[i]-'1';
	solve(n); printf("%d\n",a[1]);
	return 0;
}

最后 别有用心地 贴上暴力打表的核心代码。

	const int N = 4;
	for(a[1][1]=0; a[1][1]<3; ++a[1][1])
	for(a[1][2]=0; a[1][2]<3; ++a[1][2])
	for(a[1][3]=0; a[1][3]<3; ++a[1][3])
	for(a[1][4]=0; a[1][4]<3; ++a[1][4]){
		rep(j,2,N) rep(i,1,N-j+1)
			a[j][i] = ABS(a[j-1][i]-a[j-1][i+1]);
		if(true){ // print result to console
			rep(j,1,N){
				rep(i,2,j) putchar(' ');
				rep(i,1,N-j+1){
					writeint(a[j][i]);
					putchar(' ');
				}
				putchar('\n');
			}
		}
		int cnt[3] = {0,0,0};
		rep(i,1,N) ++ cnt[a[1][i]];
		if(cnt[1]) assert((cnt[1]&1) == a[N][1]);
		else assert((cnt[2]&1)*2 == a[N][1]);
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值