51nod-1310: Chandrima and XOR

【传送门:51nod-1310


简要题意:

  有一个数组S,保证里面的数是从小到大的,而且每一个数的二进制中都没有连续的1,如:1,2,4,5,8...

  给出n,然后给出n个位置,求出S数组中n个位置的异或和


题解:

  数位DP好题,卡了老久

  设f[i]表示2i-1到2i-1中有多少个数是在数组S中的

  转移很容易想到$f[i]=1+sum_{j=1}^{i-2}f[j]$

  而也可以得到$f[i-1]=1+sum_{j=1}^{i-3}f[j]$

  上下两式合并得到$f[i]=f[i-1]+f[i-2]$

  就是斐波那契数列,f[1]=f[2]=1

  设s[i]为1到2i-1中有多少个数是在数组S中的,显然$s[i]=sum_{j=1}^{i}f[j]$

  然后就可以做了,对于一个位置x,二分找到最大的小于x的s[m],将x-s[m]-1后再进行递归二分,复杂度为O((logn)2)

  只要在途中维护异或就行了,设P[i]为答案二进制中第i位的数,对于找到的m,将P[m]^=1就好了

  最后转换成二进制就行了,注意可能在算2的次方的时候会爆long long,所以要用快速幂


参考代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
int P[310],tot;
LL a[51000],Mod=1e9+7;
LL f[310],s[310];
void pre()
{
    tot=2;
    f[1]=1;f[2]=1;
    s[1]=1;s[2]=2;
    for(tot=3;;tot++)
    {
        f[tot]=f[tot-2]+f[tot-1];
        s[tot]=f[tot]+s[tot-1];
        if(s[tot]>1e18) break;
    }
}
void getp(LL x)
{
    if(x==0) return ;
    int l=0,r=tot,m;
    while(l<=r)
    {
        int mid=(l+r)/2;
        if(x>s[mid]) m=mid,l=mid+1;
        else r=mid-1;
    }
    P[m]^=1;
    getp(x-s[m]-1);
}
LL p_mod(LL a,LL b)
{
    LL ans=1;
    while(b!=0)
    {
        if(b%2==1) ans=ans*a%Mod;
        a=a*a%Mod;b/=2;
    }
    return ans;
}
int main()
{
    pre();
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        getp(a[i]);
    }
    LL ans=0;
    for(int i=0;i<=tot;i++) ans=(ans+LL(P[i])*p_mod(2,i))%Mod;
    printf("%lld
",ans);
    return 0;
}

 

原文地址:https://www.cnblogs.com/Never-mind/p/9776322.html