BZOJ 3329 Xorequ:数位dp + 矩阵快速幂
题意
现有如下方程:x⊕3x=2x
其中 ⊕ 表示按位异或。
共 T 组数据,每组数据给定正整数 n,任务如下:
- 求出小于等于 n 的正整数中,有多少个数是该方程的解
- 求出小于等于 2n 的正整数中,有多少个数是该方程的解,输出 mod 109+7 的值。
(n≤1018,T≤1000)
题解
第一问
方程 x⊕3x=2x 等价于 x⊕2x=3x 。
由于 x+2x=3x ,并且“按位异或”相当于“不进位加法“
所以我们可以知道,一个数 x 是该方程的解的充要条件为:x 在二进制表示下,没有两个相邻的 1 。
f[i][0/1] 表示所有二进制下长度为 i,且最右边一位为 0/1 的数中,满足没有两个相邻的 1 的数的个数。
所以有:
f[i][0]=f[i−1][0]+f[i−1][1]
f[i][1]=f[i−1][0]
边界条件为:f[0][0]=1
然后按位统计,算出小于等于 n 的答案:
- 先让 n=n+1 (因为最后算出的是小于某个数的答案,而这里要求小于等于)
- 从高到低枚举 n 的每一个二进制位 a[i]
- 如果 a[i]=1 ,那么 ans=ans+f[i][0] 。(此时累加的是第 i−1 及之前位和原数匹配时的答案)
- 如果 a[i]=1 且 a[i+1]=1 ,退出循环。因为此时的前缀已经有两个 1 相邻,之后的答案都是不合法的。
注意最终答案为 ans−1 ,因为不包含 0 这个答案。
复杂度 O(logn)
第二问
g[i] 表示二进制下长度为 i ,满足没有两个相邻的 1 的数的个数。
由于题目要求区间 [1,2i] 的答案,g[i] 表示区间 [0,2i−1] 的答案
而 0 和 2i 一定都满足条件,所以相互抵消掉了。
所以题目所求 ans=g[n]
然后考虑如何求 g[i] (分两种情况):
- g[i] 中以 0 结尾的所有数,相当于在 g[i−1] 中的所有数末尾添了一个 0
- g[i] 中以 1 结尾的所有数,相当于在 g[i−2] 中的所有数末尾添了一个 01
所以有:
g[i]=g[i−1]+g[i−2]
边界条件为:g[1]=2,g[2]=3
然后用矩阵快速幂加速转移即可:
[g[i]g[i+1]]×[0111]=[g[i+1]g[i+2]]
[g[1]g[2]]×[0111]n−1=[g[n]g[n+1]]
AC Code
#include <iostream>
#include <stdio.h>
#include <string.h>
#define MAX_L 5
#define MAX_B 70
#define MOD 1000000007
#define int long long
using namespace std;
struct Mat
{
int n,m;
int v[MAX_L][MAX_L];
Mat(int _n,int _m) { n=_n,m=_m,memset(v,0,sizeof(v)); }
Mat() { memset(v,0,sizeof(v)); }
};
int n,t;
int a[MAX_B];
int dp[MAX_B][2];
Mat get_unit(int x)
{
Mat a(x,x);
for(int i=0;i<x;i++) a.v[i][i]=1;
return a;
}
Mat mul(const Mat &a,const Mat &b)
{
Mat c(a.n,b.m);
for(int i=0;i<a.n;i++)
{
for(int j=0;j<b.m;j++)
{
for(int k=0;k<a.m;k++)
{
c.v[i][j]+=a.v[i][k]*b.v[k][j];
c.v[i][j]%=MOD;
}
}
}
return c;
}
Mat pow(Mat a,int k)
{
Mat ans=get_unit(a.n);
while(k>0)
{
if(k&1) ans=mul(ans,a);
a=mul(a,a),k>>=1;
}
return ans;
}
int cal1()
{
int t=n+1,len=0,ans=0;
while(t) a[++len]=(t&1),t>>=1;
a[len+1]=0,dp[0][0]=1;
for(int i=1;i<=len;i++)
{
dp[i][0]=dp[i-1][0]+dp[i-1][1];
dp[i][1]=dp[i-1][0];
}
for(int i=len;i>=1;i--)
{
if(a[i]) ans+=dp[i][0];
if(a[i+1] && a[i]) break;
}
return ans-1;
}
int cal2()
{
Mat a(1,2),b(2,2);
a.v[0][0]=2,a.v[0][1]=3;
b.v[0][1]=b.v[1][0]=b.v[1][1]=1;
return mul(a,pow(b,n-1)).v[0][0];
}
signed main()
{
scanf("%lld",&t);
while(t--)
{
scanf("%lld",&n);
printf("%lld\n",cal1());
printf("%lld\n",cal2());
}
}
分类:
动态规划
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具