BZOJ 3329 Xorequ (数位DP、矩阵乘法)

题目链接

https://www.lydsy.com/JudgeOnline/problem.php?id=3329

思路分析:

这道题完全是两道题拼在了一起。。
我们首先观察一下这个等式:
我们不妨可以把它移项变成 x   x o r   ( 2 x ) = 3 x x\ xor\ (2x)=3x x xor (2x)=3x
然后我们发现, 3 x = x + 2 x 3x=x+2x 3x=x+2x, 也就是 x   x o r   ( 2 x ) = x + 2 x x\ xor\ (2x)=x+2x x xor (2x)=x+2x.
并且显然有不等式 ∣ x − y ∣ ≤ x   a n d   y ≤ x   x o r   y ≤ x   o r   y ≤ x + y |x-y|\le x\ and\ y\le x \ xor\ y\le x \ or\ y\le x+y xyx and yx xor yx or yx+y, 故有 x   o r   2 x = x + 2 x x\ or\ 2x=x+2x x or 2x=x+2x, 也即 x   a n d   2 x = 0 x\ and\ 2x = 0 x and 2x=0.
这意味着什么?我们考虑一个 x x x:

x    = 00110111001010111010100111011
2x   = 01101110010101110101001110110
x^2x = 01011001011111001111101001101

观察可得, x   x o r   2 x x\ xor\ 2x x xor 2x相当于将二进制表示作了异或差分。同时,所有相邻的 1 1 1只会保留最后一个和第一个的前一个,而中间这一部分都会被变成 0 0 0. 因此,题目中的等式成立等价于 x x x的二进制表示下不存在相邻的 1 1 1.
另一种理解方式是:异或相当于二进制不进位加法,而既然是不进位加法,那么一旦进了位就会对数值有损失,当且仅当 x + 2 x x+2x x+2x不进位的时候它才等于 x   x o r   2 x x\ xor\ 2x x xor 2x. 而 2 x 2x 2x相当于把 x x x的二进制表示左移一位,进位发生当且仅当 x x x 2 x 2x 2x在同一位置上都为 1 1 1,也即 x x x有连续的至少两个 1 1 1.
完成了这一步转化之后,问题就相当于求 n n n或者 2 n 2^n 2n内有多少数的二进制表示下不存在两个连续的 1 1 1.
第二问
[ 0 , 2 n − 1 ] [0,2^n-1] [0,2n1]之内的数对应长度为 n n n 01 01 01序列,不能有连续两个 1 1 1, 这显然是斐波那契数列。(因为有一种进制叫斐波那契进制,有一个定理是任何一个整数都可以被唯一地划分成若干不连续的斐波那契数之和)严谨一些的证明是,设 F ( n ) F(n) F(n)表示长度为 n n n的满足条件的 01 01 01序列有多少个,则 F ( n ) F(n) F(n)如果第一位为 1 1 1, 那么第二位必然为 0 0 0, 后面 n − 2 n-2 n2位方案数 F ( n − 2 ) F(n-2) F(n2); 如果第一位为 0 0 0, 那么后面的位任选,方案数 F ( n − 1 ) F(n-1) F(n1). 因此 F ( n ) = F ( n − 1 ) + F ( n − 2 ) F(n)=F(n-1)+F(n-2) F(n)=F(n1)+F(n2).
但是我们刚才讨论的是 [ 0 , 2 n − 1 ] [0,2^n-1] [0,2n1], 但题目要求的是 [ 1 , 2 n ] [1,2^n] [1,2n], 显然 0 0 0必定满足条件, 2 n 2^n 2n也必定满足条件,因此答案为 F ( n ) − 1 + 1 = F ( n ) F(n)-1+1=F(n) F(n)1+1=F(n).
用矩快速幂递推即可,时间复杂度 O ( log ⁡ n ) O(\log n) O(logn), 8 8 8倍常数。
第一问
这一问对 n n n有更细致的要求,每一位都有要求,因此采取数位 d p dp dp.
d p [ i ] [ j = 0 / 1 ] [ k = 0 / 1 ] dp[i][j=0/1][k=0/1] dp[i][j=0/1][k=0/1]表示有多少个 i i i位数 ( [ 0 , 2 i − 1 ] [0,2^i-1] [0,2i1])满足条件,且第 i i i位是否必须选 0 0 0 (即如果 j j j 1 1 1则必须选 0 0 0, 否则选 0 , 1 0,1 0,1均可), k k k表示是否卡上界。考虑转移,这一位能够选 1 1 1的条件是, j j j f a l s e false false, 并且选了 1 1 1之后不会超过 n n n的限制。超过 n n n的限制的充要条件是,当前卡上界,且 n n n这一位为 0 0 0. 这一位选 0 0 0无条件转移。于是就可以成功DP了,时间复杂度 O ( log ⁡ n ) O(\log n) O(logn), 常数 4 4 4倍。
(但是由于蒟蒻太傻,写代码的时候用了大量的(!(n&(1<<(pos-1))))来判断 n n n这一位是否为 0 0 0, 因此代码十分丑陋,有的地方 7 7 7层括号套起来。。希望读者见谅)

代码实现
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define llong long long
#define ldouble long double
#define uint unsigned int
#define ullong unsigned long long
#define udouble unsigned double
#define uldouble unsigned long double
#define modinc(x) {if(x>=P) x-=P;}
#define pii pair<int,int>
#define piii pair<pair<int,int>,int>
#define piiii pair<pair<int,int>,pair<int,int> >
#define pli pair<llong,int>
#define pll pair<llong,llong>
#define Memset(a,x) {memset(a,x,sizeof(a));}
using namespace std;

llong n;

namespace Subtask1
{
 const int N = 60;
 llong dp[N+2][2][2];
 llong dfs(int pos,bool f,bool t)
 {
//  printf("DFS %d %d %d %lld\n",pos,(int)f,(int)t,dp[pos][f][t]);
  if(pos==1) return dp[pos][f][t] = 1ll+(f==false && (!(t&&(!(n&(1ll<<(pos-1)))))));
  if(dp[pos][f][t]) return dp[pos][f][t];
  llong cur = dfs(pos-1,false,(t&&(!(n&(1ll<<(pos-1))))));
  if(f==false && (!(t&&(!(n&(1ll<<(pos-1))))))) cur += dfs(pos-1,true,t);
//  printf("dfs %d %d %d %lld\n",pos,(int)f,(int)t,cur);
  return dp[pos][f][t] = cur;
 }
 void solve()
 {
  memset(dp,0,sizeof(dp));
  llong ans = dfs(N,false,true);
  printf("%lld\n",ans-1);
 }
}

namespace Subtask2
{
 const int N = 3;
 const int P = 1e9+7;
 struct Matrix
 {
  llong a[N+2][N+2];
  int sz1,sz2;
  Matrix() {}
  Matrix(int _sz)
  {
   sz1 = sz2 = _sz;
   for(int i=1; i<=sz1; i++)
   {
    a[i][i] = 1ll;
    for(int j=1; j<=sz2; j++)
    {
     if(i!=j) a[i][j] = 0ll;
    }
   }
  }
  void clear()
  {
   for(int i=1; i<=sz1; i++) for(int j=1; j<=sz2; j++) a[i][j] = 0ll;
  }
  void setsz(int _sz) {sz1 = sz2 = _sz; clear();}
  void setsz2(int _sz1,int _sz2) {sz1 = _sz1,sz2 = _sz2; clear();}
 };
  Matrix operator *(Matrix m1,Matrix m2)
 {
  Matrix ret; ret.setsz2(m1.sz1,m2.sz2);
  for(int i=1; i<=m1.sz1; i++)
  {
   for(int j=1; j<=m2.sz2; j++)
   {
    ret.a[i][j] = 0ll;
    for(int k=1; k<=m1.sz2; k++)
    {
     ret.a[i][j] += m1.a[i][k]*m2.a[k][j];
    }
    ret.a[i][j] %= P;
   }
  } return ret;
 }
 Matrix mquickpow(Matrix x,llong y)
 {
  Matrix cur = x,ret = Matrix(x.sz1);
  for(int i=0; y; i++)
  {
   if(y&(1ll<<i)) {ret = ret*cur; y-=(1ll<<i);}
   cur = cur*cur;
  } return ret;
 }
 void solve()
 {
  Matrix fib; fib.setsz(2); fib.a[1][1] = fib.a[1][2] = fib.a[2][1] = 1ll; fib.a[2][2] = 0ll;
  Matrix ori; ori.setsz2(1,2); ori.a[1][1] = ori.a[1][2] = 1ll;
  Matrix gg = ori*fib;
  Matrix fibn = ori*mquickpow(fib,n); llong ans = fibn.a[1][1];
  printf("%lld\n",ans);
 }
}

int main()
{
 int T; scanf("%d",&T);
 while(T--)
 {
  scanf("%lld",&n);
  Subtask1::solve();
  Subtask2::solve();
 }
 return 0;
}
  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
评论

打赏作者

suncongbo

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值