[JZOJ3342] 求生之路
阿新 • • 發佈:2018-11-10
Description
歷經千辛萬苦,pty終於打開了金字塔的鎖。稍稍適應了外面刺眼的光線,pty擡頭望去,眼前竟是一條不見盡頭的狹長通道。這時候背後響起了奇怪的窸窣聲,原來是金字塔內綠眼黑身的怪物追了過來。Pty來不及多想,便拼命往前奔去。通道狹窄又曲折,時不時還有斷裂,不過Pty憑藉TempleRun練成的嫻熟技巧輕鬆通過。眼看著離怪物們越來越遠時,一棵參天大樹突然聳立在了道路中央,大樹擺了擺身子,用蒼老的聲音說道:“孩子,我是遠古的守護神。你打擾了這裡的清淨,想要從我這裡通過,必須要解決一道來自遠古的問題。”
現在有n堆石子,每堆石子分別有ai個,問有多少個d使得下式成立:
守護神出的題自然是神題了,同是身為神的你可以解決麼?
Solution
由於每一位異或是獨立的,我們明顯應該考慮數位DP。
對於每一位分0/1討論,再考慮上退位的情況。
但擺在眼前的困難是,我們既不能記已經減去的d為狀態(狀態數是ai的),也不能記有哪些是需要退位的(狀態數為 )
但有一個性質非常顯然而又容易忽視。
假設我們當前做到第i位(
),有j個數需要退位,那麼這j個數一定是所有數中
位到
位最小的j個(只考慮末i位,減去同樣的d,更小的一定更有可能退位)
那麼對於每一位,我們都預處理只考慮末這麼多位排序的結果。
狀態就可以精簡成
的:
表示當前做到
這一位,有j個數需要退位。
此時只需要分0/1討論一下,統計一下1的個數是否為偶數即可。
複雜度
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);
}