[JZOJ3342] 求生之路

Description

历经千辛万苦,pty终于打开了金字塔的锁。稍稍适应了外面刺眼的光线,pty抬头望去,眼前竟是一条不见尽头的狭长通道。这时候背后响起了奇怪的窸窣声,原来是金字塔内绿眼黑身的怪物追了过来。Pty来不及多想,便拼命往前奔去。通道狭窄又曲折,时不时还有断裂,不过Pty凭借TempleRun练成的娴熟技巧轻松通过。眼看着离怪物们越来越远时,一棵参天大树突然耸立在了道路中央,大树摆了摆身子,用苍老的声音说道:“孩子,我是远古的守护神。你打扰了这里的清净,想要从我这里通过,必须要解决一道来自远古的问题。”
现在有n堆石子,每堆石子分别有ai个,问有多少个d使得下式成立:
( a 1 − d ) x o r ( a 2 − d ) x o r . . . x o r ( a n − d ) = 0 , 0 ≤ d &lt; a i (a_1-d)xor(a_2-d)xor...xor(a_n-d)=0,0\leq d&lt;a_i (a1d)xor(a2d)xor...xor(and)=0,0d<ai
守护神出的题自然是神题了,同是身为神的你可以解决么?
n ≤ 200000 , a i ≤ 1 0 18 n\leq200000,a_i\leq10^{18} n200000,ai1018

Solution

由于每一位异或是独立的,我们明显应该考虑数位DP。

对于每一位分0/1讨论,再考虑上退位的情况。

但摆在眼前的困难是,我们既不能记已经减去的d为状态(状态数是ai的),也不能记有哪些是需要退位的(状态数为 2 n 2^n 2n

但有一个性质非常显然而又容易忽视。
假设我们当前做到第i位( 2 i 2^i 2i),有j个数需要退位,那么这j个数一定是所有数中 2 i 2^i 2i位到 2 0 2^0 20位最小的j个(只考虑末i位,减去同样的d,更小的一定更有可能退位)

那么对于每一位,我们都预处理只考虑末这么多位排序的结果。
状态就可以精简成 n log ⁡ a n\log a nloga的:
F [ i ] [ j ] F[i][j] F[i][j]表示当前做到 2 i 2^i 2i这一位,有j个数需要退位。

此时只需要分0/1讨论一下,统计一下1的个数是否为偶数即可。
复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

Code

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 200005
#define LL long long
using namespace std;
int n,a1[62][N],cnt[62];
LL f[62][N],a[N],cf[61];
int main()
{
	cin>>n;
	fo(i,1,n) scanf("%lld",&a[i]),a1[0][i]=i;
	cf[0]=1;
	fo(i,1,60) cf[i]=cf[i-1]*(LL)2;
	fo(j,1,60)//方便起见这里的j=0实际上是第-1位,j=1才是2^0
	{
		int t0=0;
		fo(i,1,n) if(!(a[i]&cf[j-1])) t0++;
		fo(i,1,n) 
		{
			if(a[a1[j-1][i]]&cf[j-1]) a1[j][++t0]=a1[j-1][i];
			else a1[j][++cnt[j]]=a1[j-1][i];
		}
	}
	//上面部分为排序,记录cnt[j]表示2^(j-1)这一位为0的a的个数
	f[0][0]=1;
	fo(j,0,59)
	{
		int w=0,w1=0;
		fo(i,0,n)
		{
			if(i)
			{
				if(a[a1[j][i]]&cf[j]) w1++;
				else w++;
			}
			if((n-cnt[j+1]-w1+w)%2==0) f[j+1][w]+=f[j][i];//讨论2^j这一位选0
			if((w1+cnt[j+1]-w)%2==0) f[j+1][cnt[j+1]+w1]+=f[j][i];//讨论2^j这一位选1
		}
	}
	LL mi=1e18,v=0;
	fo(i,1,n) mi=min(mi,a[i]);
	fo(i,1,n) v^=(a[i]-mi);
	if(v==0) v=-1;
	else v=0;
	printf("%lld\n",f[60][0]+v);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值