CF551D GukiZ and Binary Operations 矩阵快速幂

题目链接: http://codeforces.com/problemset/problem/551/D

题意:

你现在需要构造一个长为 n n n 的数组 a a a ,使得 ( a 1 a n d a 2 ) ∣ ( ( a 2 a n d a 3 ) ∣ . . . ∣ ( a n − 1 a n d a n ) ) = k (a_1and a_2)|((a_2and a_3)|...|(a_{n-1}and a_n))=k (a1anda2)((a2anda3)...(an1andan))=k ,且 a i < 2 l a_i<2^l ai<2l ,问你能构造出多少个这样的数组 a a a

做法:

我们将 k k k 用二进制进行拆分,对于某一位 i i i ,假设

  • k k k 的这一位为 0 0 0,那么就表示这一位在数组中没有两个连续的 1 1 1
  • k k k 的这一位为 1 1 1,那么就表示这一位在数组中至少出现一次两个连续的 1 1 1

那么假设我们要算没有出现两个连续的 1 1 1 的情况数有几个,对于 a 1 a_1 a1 来说,这一位可以是 0 0 0 或者 1 1 1 ,如果是 0 0 0 ,那么下一个可以出现 1 1 1 0 0 0 ,否则下一个只能出现 0 0 0

用矩阵表示即
在这里插入图片描述
最后得出的两个数相加即为不能出现连续两个 1 1 1 的方案数,另一种情况只需要 2 n 2^n 2n 减去这个数量即可。

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define rep_e(i,u,v) for(int i=head[u],v=to[i];~i;i=nex[i],v=to[i])
using namespace std;
typedef long long ll;
const int maxn=305;
const int maxm=100005;
ll n,k,mod,l;
struct mat{
    ll a[5][5];
    void Clear(){
        memset(a,0,sizeof(a));
        rep(i,1,2){
            a[i][i]=1;
        }
    }
};
mat mul(mat a,mat b){
    mat ans;
    memset(ans.a,0,sizeof(ans.a));
    rep(i,1,2)
        rep(j,1,2)
            rep(k,1,2)
                ans.a[i][j]=(ans.a[i][j]+a.a[i][k]*b.a[k][j])%mod;
    return ans;
}
ll quick(ll a,ll b){
    ll ans=1;
    while(b){
        if(b&1) ans=ans*a%mod;
        a=a*a%mod;
        b/=2;
    }
    return ans;
}
mat Quick(mat a,ll k){
    mat ans;
    ans.Clear();
    while(k){
        if(k&1) ans=mul(ans,a);
        a=mul(a,a);
        k/=2;
    }
    return ans;
}
int main(){
    scanf("%lld%lld%lld%lld",&n,&k,&l,&mod);
    ll num0=0,num1=0;
    for(ll i=0;i<min(l,63ll);i++){
        if(k&(1ll<<i))num1++;
        else num0++;
    }
    if(l==64) num0++;
    if(l<63){
        if(k>=(1ll<<l)){
            return 0*printf("0\n");
        }
    }

    mat now,tmp;
    memset(now.a,0,sizeof(now.a));
    now.a[1][1]=now.a[1][2]=now.a[2][1]=1;
    memset(tmp.a,0,sizeof(tmp.a));
    tmp.a[1][1]=tmp.a[2][1]=1;

    mat fin=Quick(now,n-1);
    fin=mul(fin,tmp);
    ll no_s1=(fin.a[1][1]+fin.a[2][1])%mod;
    ll yes_s1=(quick(2ll,n)+mod-no_s1)%mod;
    ll ans=(quick(no_s1,num0)%mod*quick(yes_s1,num1)%mod)%mod;
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值