题目描述
历经千辛万苦,pty终于打开了金字塔的锁。稍稍适应了外面刺眼的光线,pty抬头望去,眼前竟是一条不见尽头的狭长通道。这时候背后响起了奇怪的窸窣声,原来是金字塔内绿眼黑身的怪物追了过来。Pty来不及多想,便拼命往前奔去。通道狭窄又曲折,时不时还有断裂,不过Pty凭借TempleRun练成的娴熟技巧轻松通过。眼看着离怪物们越来越远时,一棵参天大树突然耸立在了道路中央,大树摆了摆身子,用苍老的声音说道:“孩子,我是远古的守护神。你打扰了这里的清净,想要从我这里通过,必须要解决一道来自远古的问题。”
现在有n堆石子,每堆石子分别有ai个,问有多少个d使得下式成立:
数位DP
我们可以设f[i,j]表示做到第i位,当前有j个退位的可选d值。
因为要处理退位,所以从低位到高位做。
如果知道退位情况?注意到在第i位要退位的一定是按照后i-1位排序后的前j个。
这个就易证了。因为要退位代表其小于减去的数,那么当然是排序后的前j个啦。
每次增加一个最高位然后维护排序非常容易,可以像实现CDQ那样记录一个right。
然后看看第i位填0还是1。
只要在最高位不退位,那么就可以保证d小于等于最小ai。而题目d不能等于最小ai,所以最后再判一判。
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=200000+10,maxd=60;
ll f[maxd+10][maxn],two[maxd+10];
ll a[maxn],b[maxn],c[maxn],sum[maxn];
bool right[maxn];
ll i,j,k,l,t,n,m;
int main(){
two[0]=1;
fo(i,1,maxd) two[i]=two[i-1]*2;
scanf("%lld",&n);
fo(i,1,n) scanf("%lld",&a[i]),b[i]=i;
f[0][0]=1;
fo(i,0,maxd){
fo(j,1,n) right[j]=sum[j]=(a[b[j]]/two[i])%2;
t=0;
fo(j,1,n)
if (!right[j]) c[++t]=b[j];
fo(j,1,n)
if (right[j]) c[++t]=b[j];
fo(j,1,n) sum[j]+=sum[j-1];
fo(j,1,n) b[j]=c[j];
fo(j,0,n)
if (f[i][j]){
if ((sum[n]+j-sum[j]*2)%2==0) f[i+1][j-sum[j]]+=f[i][j];
if ((2*sum[j]-j)%2==0) f[i+1][n-sum[n]+sum[j]]+=f[i][j];
}
}
t=0;
k=a[1];
fo(i,2,n) k=min(k,a[i]);
fo(i,1,n) t^=(a[i]-k);
if (!t) f[maxd+1][0]--;
printf("%lld\n",f[maxd+1][0]);
}