codeforces 551d[补]

BNUZ比赛训练【补】

其实对于这种,真的看不是很懂的题。。我真的不是很想补的,做完这道题,感觉自己数学智商给碾压了。。一道纯数论题,用到矩阵快速幂+快速幂,但是至今不知道这公式是怎么推出来的。。来日方长,看了一个早上,说不定日后哪天就醒悟了。


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

题目大概意思:
给你n,k,l,m分别代表,一个集合里有n个数,这个集合里的数不能超过2^l 那么大,且每一个数都只能使用一次,有如下运算
(a1 & a2) | (a2 & a3) | ….. | (an-1 & an)
如果这个式子的值等于k则这个为作者想要的表达式,现在问这个集合有多少个,求出来的答案mod m

乍一眼看,好难啊,不会,其实,我还真的是不会。。。只是看见m <= 10^9 + 7这个长得非常像费马小经常用的那个值,但是他是区间啊,算了,也就只是随便yy下。

然后对于,1uLL << l 这个数,如果他比k还要小,说明无论怎么进行位运算都无法到达k了。所以直接输出0,同时,对于64这个l,1uLL << 64 会爆。。所以特判一下。

然后下面的,我就都不会了。。。对于k,把k转换成二进制,有以下答案:
对于位值是0,则ans * f[n];
对于位值是1,则ans * (2 ^ n - f[n])
然后就出答案了,我的天。。。看着别人题解套板套过的。。至今不能理解。好深奥哎。。


补【后传】

经过Q巨的精心指导,本渣,终于理解了这道题。。。
首先分析该运算(a1 & a2) | (a2 & a3) | …. |(an-1 & an)
对于k的每一位二进制的位数,都是a1,a2。。。an每一位运算出来的结果
辣么如果想运算出来是0的话,a1和a2、a2和a3。。。an-1和an,在同一个括号里的就不能该第i位都为1,也就是相邻的数,二进制数的第i位不能都为1,辣么我们对n个数dp一下
当长度为0时,相邻不为1的方案数为1,
当长度为1时,相邻不为1的方案数为2,
当长度为2时,相邻部位1的方案数为3,
对于第i位,他的相邻方案数不为1可以由第i - 1位的补0或者补1,第i - 2位补01或者补10可得,
所以有dp[i] = dp[i - 1] + dp[i - 2]
所以对于n位数,他能制造出的相邻不为1的方案数为一个fib序列,那么他能制造出的相邻为0的方案数为dp[n],他能制造出的相邻为1的方案数为2^n - dp[n],所以,上面那东西原来是这么来的。
然后再对k的二进制数每一位进行分解,当为0的时候,就是要制造成0的方案数,当为1的时候,就是要制造成1的方案数,所以只要满足k能被制造出来时,方案数一定为1,然后再乘上每一位能制造出来的1或者0的方案数即可出答案。
dp[i] = dp[i - 1] + dp[i - 2]明显是个fib序列,用矩阵快速幂搞一搞,
2 ^ n用快速幂搞一搞,不能直接运算,会爆ull

最后感谢Q神,此题解终结。


附上渣渣的代码

/*
@resouces: codefoces 551D
@date: 2017-3-6
@author: QuanQqqqq
@algorithm: 矩阵快速幂 
*/
#include <bits/stdc++.h>

#define N 2
#define ll long long
#define MOD 10000

using namespace std;
struct mat{
    ll mapp[N][N];
};

mat res = {1,0,0,1},init = {1,1,1,0};

mat mul_mat(mat a,mat b,ll mod){
    mat c;
    for(int i = 0;i < N;i++){
        for(int j = 0;j < N;j++){
            c.mapp[i][j] = 0;
            for(int k = 0;k < N;k++){
                (c.mapp[i][j] += a.mapp[i][k] * b.mapp[k][j] % mod) %= mod;
            }
        }
    }
    return c;
}

mat ksm_mat(mat n,ll k,ll mod){
    mat t = n,ans = res;
    while(k){
        if(k & 1)
            ans = mul_mat(ans,t,mod);
        t = mul_mat(t,t,mod);
        k >>= 1;
    }
    return ans;
}

ll ksm(ll n,ll k,ll mod){
    ll temp = n,ans = 1;
    while(k){
        if(k & 1)
            ans =  (ans * temp) % mod;
        temp = (temp * temp) % mod;
        k >>= 1;
    }
    return ans % mod;
}

int main(){
    mat ma;
    ll n,k,l,m,ans;
    while(~scanf("%lld %lld %lld %lld",&n,&k,&l,&m)){
        ans = 0;
        if(l == 64 || (1uLL << l) > k){
            ans++;
            ma = ksm_mat(init,n + 1,m);
            ll t1 = ma.mapp[0][0];
            ll t2 = (m + ksm(2,n,m) - t1) % m;
            for(ll i = 0;i < l;i++){
                if(k & (1uLL << i)){
                    ans = (ans * t2) % m;
                } else {
                    ans = (ans * t1) % m;
                }
            }
        }
        printf("%lld\n",ans % m);
    }
}

望有缘人相助,或者自己日后某天突然醒悟再回首观看此数论。。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值