bzoj3329 Xorequ

bzoj3329


题目

3329:Xorequ

TimeLimit:1Sec
MemoryLimit:256MB

Description

给定正整数 N
现有如下方程
x3x=2x
其中 表示按位异或
任务如下:
1.求出 1 ~n中有多少个数是该方程的解
2.求出 1 ~2n中有多少个数是该方程的解,模 109+7

Input

第一行一个正整数,表示数据组数 T ,接下来T
每行一个正整数 N

Output

2T
2i1 行表示第 i 个数据中问题一的解
2i行表示第 i 个数据中问题二的解

SampleInput

1
1

SampleOutput

1
2

HINT

x=1 x=2 都是原方程的根
注意第一个问题的解
不要模 109+7
1N1018
1T1000


题解

因为 N1018 ,所以可以想到时空复杂度约为 O(logN)
这样子,有经验的老人家们就能想到矩阵乘法,进而想到矩阵优化。
然而,矩阵优化的前提是动态规划……然而,我们并未想到动态规划。
观察方程 x3x=2x ,可以变为 x2x=3x
又因为 x+2x=3x ,所以可以得到 x 的二进制数没有连续的1
至于为什么呢?博主也不知道……就稍微水水地证明一下。
x+y 为进位加法, xy 为不进位加法。
所以当 x+y=xy 时, x y没有一位同时为 1
又因为y=2x,所以可以得到 x 的二进制数没有连续的1
接下来就是比较明显的数位DP。
f(i,j) 表示一个 i 位的数以j结尾的方案数。
那么 f(0,1)=0,f(0,0)=1f(i,1)=f(i1,0),f(i,0)=f(i1,0)+f(i1,1)
仔细观察发现 j=0 j=1 时刚好错位。
所以可以把它合成一维。
g(i)=f(i,0)=f(i+1,1) ,那么 g(0)=g(1)=1,g(i)=g(i1)=g(i2)
因为这个DP方程是线性的,所以可以矩阵优化。
那么第一问用数位DP,第二问用矩阵优化,时空复杂度均为 O(logN)
(没有学过矩阵优化的朋友们建议写一下模板题 luogu1962


总结

对于 N1018 这样的数据范围的题目,可以想到时空复杂度约为 O(logN)
这种时候就要想到倍增类型的做法,如本题的数位DP和矩阵优化。
为了使用矩阵优化,就要想办法转移成广义斐波那契数列。


标程

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll MOD=1000000007;
const ll N=65;
ll t, n, s[N], f[N];
struct matrix{
    ll a[2][2];
    matrix(){memset(a, 0, sizeof(a));}
    matrix operator *(const matrix& b)const
    {
        matrix c;
        for (ll i=0; i<=1; i++)
            for (ll j=0; j<=1; j++)
                for (ll k=0; k<=1; k++)
                    c.a[i][j]=(c.a[i][j]+1LL*a[i][k]*b.a[k][j]%MOD)%MOD;
        return c;
    }
};
matrix power(matrix a, ll b)
{
    matrix c=a; b--;
    while (b)
    {
        if (b&1) c=c*a;
        b>>=1; a=a*a;
    }
    return c;
}
void init()
{
    s[1]=f[0]=f[1]=1;
    for (ll i=2; i<=60; i++)
    {
        s[i]=s[i-1]<<1;
        f[i]=f[i-1]+f[i-2];
    }
}
void work1(ll x)
{
    ll ret=0, w=60; x++;
    while (s[w]>x) w--;
    ll cur=(x>>(w-1)), pre;
    for (ll i=1; i<w; i++)
        ret+=f[i-1];
    pre=cur; x^=(cur<<(w-1));
    for (ll i=w-1; i; i--)
    {
        cur=(x>>(i-1));
        if (cur) ret+=f[i];
        if (cur && pre) break;
        pre=cur; x^=(cur<<(i-1));
    }
    printf("%lld\n", ret);
}
void work2(ll x)
{
    matrix ans; ans.a[0][0]=ans.a[0][1]=ans.a[1][0]=1;
    ans=power(ans, x+1); printf("%lld\n", ans.a[0][0]);
}
int main()
{
    init(); scanf("%lld", &t);
    while (t--)
    {
        scanf("%lld", &n);
        work1(n); work2(n);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值