bzoj1411 硬币游戏 分治

        显然如果令正面=0,反面=1,那么中间硬币的值为两边硬币的异或。然后我们来找规律>_<:

       我们首先假设这个序列为a[1],0,a[3],0,a[5],0,a[7],0,a[9],0,a[11],...,然后把每一次的值弄出来(0省略不写,假设a[1]左边是a[-1],a[-1]左边是a[-3]方便找规律):

       a[1] a[3] a[5] a[7] a[9] a[11]

       a[-1]^a[1] a[1]^a[3] a[3]^a[5] a[5]^a[7] a[7]^a[9] a[9]^a[11]

       a[-3]^a[1] a[-1]^a[3] a[1]^a[5] a[3]^a[7] a[5]^a[9] a[7]^a[11] a[9]^a[13]

      然后我们发现翻两次之后有:a[i]=a[i-2]^a[i+2],那么我们不妨设不为空的序列比如a[1],a[3],a[5],a[7]把它压成a[1],a[2],a[3],a[4],接下来一次翻两步,就变成:

       a[1] a[2] a[3] a[4]

       a[0]^a[2] a[1]^a[3] a[2]^a[4] a[3]^a[5]

       翻四步第的结果,相当于再翻两下,变成:

       a[0]^a[2] a[1]^a[3] a[2]^a[4] a[3]^a[5]

       a[-2]^a[4] a[-1]^a[5] a[1]^a[5] a[2]^a[6]。

       类似的就可以得到翻8,16,32,64步之后的结果,就可以发现对于任意得k,我们得到翻了2^k以后的结果为

       a[i]=a[i-2^(k-1)]^a[i+2^(k-1)]。可以用归纳法证明。

       然后就可以根据T的二进制位进行分治了,用类似于快速幂的方式即可。注意当T为奇数时可以先翻一步,然后T=T-1。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define N 100005
using namespace std;

int n,a[2][N]; ll m;
int main(){
	scanf("%d%lld",&n,&m); int i,k,flag=0;
	for (i=1; i<=n; i++){
		scanf("%d",&a[0][i]); a[0][i]--;
	}
	if (m&1){
		m--; flag=1; a[0][n+1]=a[0][1];
		for (i=1; i<=n; i++) a[0][i]^=a[0][i+1];
	}
	int last=0,now=0;
	for (m>>=1,k=1; m; m>>=1,k=(k<<1)%n)
		if (m&1){
			last=now; now^=1;
			int x=(n-k)%n+1,y=k+1;
			for (i=1; i<=n; i++){
				a[now][i]=a[last][x]^a[last][y];
				x=x%n+1; y=y%n+1;
			}
		}
	if (flag) printf("0 ");
	for (i=1; i<n; i++) printf("%d 0 ",a[now][i]+1);
	if (flag) printf("%d\n",a[now][n]+1); else printf("%d 0\n",a[now][n]+1);
	return 0;
}


by lych

2016.2.27

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值