传送门:http://codeforces.com/contest/691/problem/E
题意:给定序列,从序列中选择k(1≤k≤1e18)个数(可以重复选择),使得得到的排列满足xi与xi+1异或的二进制表示中1的个数是3的倍数。问长度为k的满足条件的 序列有多少种?
思路:首先每个元素自己构成一个长度为1的满足条件的序列。
其次我们可以预处理出满足条件的vi,vj,就可以得到一个横纵为n的01矩阵。这还是很显然的。此时我们得到了以vi开头,vj结尾的长度为2的序列个数。
接下来我们发现,两个矩阵相乘,矩阵c为新得到的矩阵,此时矩阵a=b,c[i][j]=a[i][1]∗b[1][j]+a[i][2]∗b[2][j]+...+a[i][n]∗b[n][j],我们得到的即为以ai开头,aJ结尾的长度为3的序列个数!
接下来用矩阵c更新矩阵a,再与最初的01矩阵,即b相乘,得到的又为开头元素为ai,结尾元素为aj的长度为4的序列个数!
依次乘k−1次即得到结果,这部分可以用矩阵快速幂进行优化。
最后把得到的矩阵中的每个元素的值加起来即为长度为k的满足条件的序列个数!
实质上就是floyd求长度为k的道路。
巧妙的利用矩阵乘法的性质解决问题!
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll __int64
#define mod 1000000007
const int N=105;
int n;
ll k, a[N];
struct Matrix{
ll a[N][N];
Matrix(){
memset(a,0,sizeof(a));
}
void init(){
for(int i=1;i<=n;++i)a[i][i]=1;
}
Matrix operator *(const Matrix &rhs)const{
Matrix res;
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
for(int k=1; k<=n; k++)
res.a[i][j]=(res.a[i][j]+a[i][k]*rhs.a[k][j]%mod)%mod;
return res;
}
Matrix operator ^(ll mi)const{
Matrix tmp=(*this),res;
res.init();
while(mi){
if(mi&1)res=res*tmp;
tmp=tmp*tmp;
mi>>=1;
}
return res;
}
};
int main(){
scanf("%d%lld", &n, &k);
for(int i=1; i<=n; i++)
scanf("%lld", &a[i]);
Matrix A;
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
if(__builtin_popcountll(a[i]^a[j])%3 == 0){
A.a[i][j]=1;
}
}
}
A=A^(k-1);
ll ans=0;
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
ans = (ans+A.a[i][j])%mod;
}
}
printf("%lld\n", ans);
return 0;
}