2018 徐州网络赛 Hard to prepare (递推)

题意

有n个人围成一圈,现有一个k位二进制数,表示第i种帽子,所以一共有 2k 2 k 种帽子。现在将这些个帽子分配给n个人,要求相邻的两个人之间的帽子所代表的二进制数,他们的同或和要大于0,问一共有多少种分配方法。

分析

看起来也像是一个高中的组合数学题目。手动模拟一下可以发现,对于每一个二进制数,有且仅有一个与之对应的二进制数跟他的同或和为0,因此如果每个人旁边只有一种帽子不能放,其他的都可以放。假设有m种帽子, m=2k m = 2 k

第一个人有m种放法,第二人不能跟跟他同或为0,所以只有m-1种放法,以此类推,第n-1个人也有m-1种放法。然后是最后一个,因为最后一个不仅取决于第n-1个还取决于第1个,所以我们还要看第n-1个和第1个情况

1.如果第n-1个与第1个不相同的话,那么还是按照我们上面说的,第1个人放m种,第2个人放m-1…第n-1个人放m-1,最后一个人因为既要和第1个同或大于0,又要和第n-1个同或大于0,所以只有m-2种。那么一共就是 m(m1)..(m1)(m2) m ( m − 1 ) . . ( m − 1 ) ( m − 2 )

2.如果第n-1个与第1个相同的话,那么这个时候我们这样看。首先把问题分解成
(在第1个人和第n-1个人相同的情况下,前n-1个人的种类)*(第n个人能分到的种类)
然后先看后面的“第n个人分到的种类”,显然由于第1个和第n-1个人是一样的,那么第n个人的种类就是m-1.
那前面的种类怎么算呢?前提条件是第1个和第n-1个是一样的,然后中间随便放,而且题目要求的是围成一圈,所以我们画个图看看,会发现首位是一样的,那么我们可以将他们看成是一个整体,因为对结果没有影响。那现在前面一半的种类数就化成了当n=n-2时候的一个子问题。

代码中用递归的形式给出了递推公式

代码

#include 
using namespace std;
typedef long long int ll;
const ll mod = 1e9 + 7;
ll n, k, m;
ll pow_m(ll a, ll b)
{
    ll res = 1;
    while (b)
    {
        if (b & 1) res = res * a%mod;
        a = a * a%mod;
        b = b >> 1;
    }
    res = res % mod;
    return res;
}
ll solve(int n)
{
    if (n == 1) return m;
    if (n == 2) return m * (m - 1) % mod;
    return  (m*pow_m(m - 1, n - 2) % mod*(m - 2) + solve(n - 2)) % mod;
}
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%lld%lld", &n, &k);
        m = pow_m(2, k);
        printf("%lld\n", solve(n));
    }
    return 0;
}

你可能感兴趣的:(递推)