题意:
有n个点,每个点可从[0~2^k-1]中任取一值,n个点围成一个圆,要求相邻两点值的nxor(同或)值不为0,求方案数
思路:
根据题意可推出,对于一个值X,只有唯一一个值~X不能与其相邻
对于第一个点,有2^k种选择
对于第2~n-1个点,每个位置有2^k-1种选择,共有(2^k-1)^n-2种选择
对于第n个点,因其不能与第n-1个点和第1个点nxor为0,所以有2^k-2种选择
由上述三点可推出,N=2^k*[(2^k-1)^(n-2)]*(2^k-1)
由于每个点的值是任取的,所以还可能存在第1个点的值与第n-1个点的值相同的情况,此时第n个点只有2^k-1种选择
对于这种情况的状态数,就相当于在一个由n-2个点构成的环上,将第1个点拆分成两个相同的点——第1点和第n-1点,然后在第n-1点后添加第n点
所以此时的情况数为F(n-2,k)
结合上述两种情况,最终得出方案数:F(n,k)=2^k*[(2^k-1)^(n-2)]*(2^k-1)+F(n-2,k)
其它方法:
由于使用递归自顶向下求解,显然可以改为自底向上的动态规划做法,但递归做法更符合组合数学的推理过程,更容易想到
代码:
#include <iostream>
using namespace std;
typedef long long LL;
const long long mod=1e9+7;
LL pow(LL a,LL b) //快速幂
{
LL ans=1;
while(b)
{
if(b%2)
ans=ans*a%mod;
a=a*a%mod;
b/=2;
}
return ans;
}
LL p[1000005]={1}; //存储2^k
LL F(LL n,LL k)
{
LL ans;
if(n==2)
return p[k]*(p[k]-1)%mod;
if(n==1)
return p[k];
ans=(p[k]*pow((p[k]-1),n-2)%mod*max(p[k]-2,0LL)%mod+F(n-2,k))%mod;
return ans;
}
int main()
{
int t;
for(int i=1;i<1e6+5;i++)
p[i]=p[i-1]*2%mod;
cin>>t;
while(t--)
{
int n,k;
cin>>n>>k;
cout<<F(n,k)<<endl;
}
return 0;
}