求生之路

题目描述

历经千辛万苦,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]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值