题目
n(n<=1e6)个人围成一个环,每个人可以从0到(k<=1e6)这些数里选一个数,每个数可以被重复选
但当前人i不能和他相邻的人j选的数的同或值为0,求满足题意的方案数%(1e9+7)
同或值为0,即所有对应位置二进制位值都相反,如4和3同或值就为0
思路来源
https://blog.youkuaiyun.com/Originum/article/details/82598329
题解
记dp[i][0]为长度为i的首尾值相同的方案数(形如x ... x)
dp[i][1]为长度为i的首尾值同或值为0(不妨称为相反)的方案数(形如 x ... x非)
dp[i][2]为长度为i的首尾既不相同也不相反的方案数(形如x ... y,其中y不等于x,y不等于x非)
x ... x 可以后缀x 形成dp[i+1][0],x ... y可以后缀x 形成dp[i+1][0];
x ... x非 可以后缀 x非 形成dp[i+1][1],x ... y可以后缀 x非形成 dp[i+1][1];
x ... x 可以后缀y(y有m-2种取法,除去x、x非) 形成dp[i+1][2];
x ... x非 可以后缀 y(y有m-2种取法,除去x、x非)形成dp[i+1][2];
x ... y 可以后缀 y'(y'有m-3种取法,除去x、x非、y非)形成dp[i+1][2];
dp[i][0]=(dp[i-1][0]+dp[i-1][2])%mod;
dp[i][1]=(dp[i-1][1]+dp[i-1][2])%mod;
dp[i][2]=(dp[i-1][0]*(m-2)%mod+dp[i-1][1]*(m-2)%mod+dp[i-1][2]*(m-3)%mod)%mod;
乘法部分注意一下,不要爆ll,取模的地方尽量写规范
心得
递推式dp主要定义状态比较难,和数位dp给人的感觉是一致的
又复习了一下快速乘吧,
快速乘,用减法代替取模,用加法代替乘法,其余和快速幂基本一致
dp[2][1]=0看似很迷,毕竟画图的话dp[2][1]应该是等于m的
实则用dp[i][1]=dp[i-1][1]+dp[i-1][2]可以解释,所有项都该是符合递推式的鸭
就这样吧,无需深究
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=1e6+10;
int t;
ll n,k;
ll m,dp[maxn][3];
ll modmul(ll x,ll n,ll mod)
{
x%=mod;n%=mod;
ll res=0;
for(;n;n/=2)
{
if(n&1)
{
res+=x;
if(res>=mod)res-=mod;
}
x+=x;
if(x>=mod)x-=mod;
}
return res;
}
ll modpow(ll x,ll n,ll mod)
{
ll res=1;
for(;n;n/=2)
{
if(n&1)res=modmul(res,x,mod);
x=modmul(x,x,mod);
}
return res;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%lld%lld",&n,&k);
m=modpow(2,k,mod);
dp[1][0]=m;dp[1][1]=0;dp[1][2]=0;
dp[2][0]=m;dp[2][1]=0;dp[2][2]=modmul(m,m-2,mod);
for(int i=3;i<=n;++i)
{
dp[i][0]=(dp[i-1][0]+dp[i-1][2])%mod;
dp[i][1]=(dp[i-1][1]+dp[i-1][2])%mod;
dp[i][2]=(dp[i-1][0]*(m-2)%mod+dp[i-1][1]*(m-2)%mod+dp[i-1][2]*(m-3)%mod)%mod;
}
printf("%lld\n",(dp[n][0]+dp[n][2])%mod);
}
return 0;
}