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);
}
}
望有缘人相助,或者自己日后某天突然醒悟再回首观看此数论。。