1. 程式人生 > >快速冪+地推

快速冪+地推

完全 我們 由於 一個 https math 不難 pow 二進制位

題目傳送門

題意:有n個人坐成一圈,每個人都戴著一個面具,面具有0-2^k-1種編號,每相鄰的兩個人的面具的編號的二進制表示中必須存在至少一位相同,問總共有多少種排列方法。

思路:

我們可以把這個圈從某一處裁開,使之變成一條線,令長度為n的直線上編號兩兩之間有相同的二進制位的排列方法為line(n),編號兩兩之間有相同的二進制位但是首位的二進制表示完全不同,記為linedf(n)。則當圓的周長為n時的答案就是

line(n)-linedf(n)

再來推導line(n),由於只需要與前一個人的面具的編號不是完全不相同就可以了。因此

line(n)=line(n-1)*(2^k-1)

再來推導linedf(n),由於linedf(n)指的是第n個人和第一個人完全不相同的情況,因此,第n-1位不能與第一位相同,那麽linedf(n)=line(n-1)-第n-1位與第一位完全相同的情況數。不難發現,第一位與x位完全相同就能使1到x-1連成一個環,因此第n-1位與第一位完全相同的情況數就是line(n-2)-linedf(n-2)。所以:

linedf(n)=line(n-1)-(line(n-2)-linedf(n-2));

#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
typedef long long LL;
const LL mod=1e9+7;
const LL MAX_E=1e6+5;
LL p;
LL dp[MAX_E],fals[MAX_E];
LL mod_pow(LL x,LL n)
{
    LL res=1;
    while(n>0)
    {
        
if(n&1) res=res*x%mod; x=x*x%mod; n >>= 1; } return res; } LL solve(LL n,LL k) { for(int i=2;i<=n;i++) { dp[i]=dp[i-1]*(mod_pow(2,k)-1)%mod; fals[i]=(dp[i-1]-(dp[i-2]-fals[i-2]))%mod; } return (dp[n]+mod-fals[n])%mod; }
int main() { int T; cin>>T; while(T--) { LL n,k; cin>>n>>k; p=mod_pow(2,k); dp[0]=dp[1]=p; cout<<solve(n,k)<<endl; } }

快速冪+地推