1. 程式人生 > >[JZOJ3342] 求生之路

[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 < a i (a_1-d)xor(a_2-d)xor...xor(a_n-d)=0,0\leq d<a_i
守護神出的題自然是神題了,同是身為神的你可以解決麼?
n 200000 , a i 1 0 18 n\leq200000,a_i\leq10^{18}

Solution

由於每一位異或是獨立的,我們明顯應該考慮數位DP。

對於每一位分0/1討論,再考慮上退位的情況。

但擺在眼前的困難是,我們既不能記已經減去的d為狀態(狀態數是ai的),也不能記有哪些是需要退位的(狀態數為 2 n 2^n

但有一個性質非常顯然而又容易忽視。
假設我們當前做到第i位( 2 i 2^i ),有j個數需要退位,那麼這j個數一定是所有數中 2 i 2^i 位到 2 0 2^0 位最小的j個(只考慮末i位,減去同樣的d,更小的一定更有可能退位)

那麼對於每一位,我們都預處理只考慮末這麼多位排序的結果。
狀態就可以精簡成 n log a n\log a 的:
F [ i ] [ j ] F[i][j] 表示當前做到 2 i 2^i 這一位,有j個數需要退位。

此時只需要分0/1討論一下,統計一下1的個數是否為偶數即可。
複雜度 O ( n log n ) O(n\log n)

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);
}