1. 程式人生 > >【組合數學dp】ACM-ICPC 2018 徐州賽區網路預賽 A. Hard to prepare

【組合數學dp】ACM-ICPC 2018 徐州賽區網路預賽 A. Hard to prepare

Step1 Problem:

給你 n 個數排成一圈,每個數的範圍[0, 2^k-1],相鄰兩個數字它們異或值不能為 2^(k-1),求滿足條件的排列數。
資料範圍:
T<=20, 0 < n, k<=1e6.

Step2 Ideas:

我們假設 a 不能與他相鄰的數為 a1,也就是 a 異或 a1 = 2^(k-1).
第一反應:如果不是圈,那麼排列數 = 4*3^(n-1).

我們把第一個是 a 最後一個是 a1 的方案數也算進去了。
第一個思考點:開始思考最後一個不能是 a1 的方案數怎麼求。
如果我們知道第 n-1 位和 第 1 位一樣的方案數 s1第 n-1 位和 第 1 位不一樣的方案數 s2


那麼排列數 = s1*(k-1) + s2*(k-2).
第二個思考點:就開始思考第 n-1 位和 第 1 位一樣的方案數怎麼求。
然後你會發現你需要知道第 1 位和第 n-2 位 異或值為 2^(k-1) 的方案數。
所以:
dp[n][0]: 代表第 1 位和第 n 位一樣的方案數
dp[n][1]: 代表第 1 位和第 n 位異或值位 2^(k-1) 的方案數
dp[n][2]: 剩下情況的方案數

Step3 Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6+5;
const int MOD = 1e9+7;
ll dp[N][3];
ll Pow(ll x, ll n)
{
    ll ans = 1;
    while(n)
    {
        if(n&1) ans *= x, ans %= MOD;
        x = x*x, x %= MOD;
        n >>= 1;
    }
    return ans;
}
int main()
{
    int T, n, k;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d %d", &n, &k);
        k = Pow(2, k); ll t = k;
        dp[1][0] = k; dp[1][1] = dp[1][2] = 0;
        for(int i = 2; i <= n; i++)
        {
            t = t*(k-1)%MOD;
            dp[i][0] = dp[i-1][0] + dp[i-1][2], dp[i][0] %= MOD;
            dp[i][1] = dp[i-1][1] + dp[i-1][2], dp[i][1] %= MOD;
            dp[i][2] = ((t-dp[i][0]-dp[i][1])%MOD+MOD)%MOD;
        }
        printf("%lld\n", (dp[n][0]+dp[n][2])%MOD);
    }
    return 0;
}