题解 | #Paint Box#

Paint Box

https://ac.nowcoder.com/acm/problem/13884

  • 快速幂
  • 排列组合
  • 容斥原理及二项式反演
  • 题解

快速幂

对于一个数的次幂,如:2102^{10}。最常规的计算方法是将2乘以10次,时间复杂度为:O(NN)。
如果我们将它写成二进制:2101022^{{1010}_2},很自然的可以分解成2100022^{{1000}_2}21022^{{10}_2}两个数相乘,即:28222^{8}*2^{2}。这样,我们可以在计算282^8时使用已经计算完毕的242^4,所以我们只需要将底数不断的平方就可以计算出答案,时间复杂度:O(log2Nlog_2N)。

long long qpow(long long n, long long m)
{
    long long ans = 1;
    while (m)
    {
        if (m & 1)
            ans *= n;
        n *= n;
        m >>= 1;
    }
    return ans;
}

排列组合

1.逆元

自然数a,p互质,如果满足a*b%p=1,那么b为a%p的逆元

2.小费马定理

公式:a与p互质,ap1%p=1a^{p-1}\%p=1
证明:
1.如果自然数c和p互质,ac%p=bc%pa*c\%p=b*c\%p,那么我们可以得到a%p=b%pa\%p=b\%p
a=lp+qa=l*p+qb=rp+qb=r*p+q \Rightarrow a%p=b%pa\%p=b\%p \Leftrightarrow ab=pa-b=p* (lr)\left (l-r)\right.
ac%p=bc%pa*c\%p=b*c\%p
\Rightarrow (acbc)\left (a*c-b*c)\right. %p=0\%p=0
\Rightarrow (ab)\left (a-b)\right. c%p=0*c\%p=0
因为c和p是互质的,\Rightarrow (ab)\left (a-b)\right. %p=0\%p=0
\Rightarrow a%p=b%pa\%p=b\%p
2.定义集合c1,c2,c3,c4...cp1c_{1},c_{2},c_{3},c_{4}...c_{p-1}是p的一个完全剩余系,a与p互质,那么ac1,ac2,ac3...acp1a*c_{1},a*c_{2},a*c_{3}...a*c_{p-1}也是p的一个完全剩余系。
假设acn%p=acm%pa*c_{n}\%p=a*c_{m}\%p,那么cn%p=cm%pc_{n}\%p=c_{m}\%p。所以命题成立。
1234...1*2*3*4*...* (p1)\left (p-1)\right. %p=ap11234...\%p=a^{p-1}*1*2*3*4*...* (p1)\left (p-1)\right. %p\%p
显然1234...1*2*3*4*...* (p1)\left (p-1)\right.pp互质,所以ap1%p=1%pa^{p-1}\%p=1\%p
证毕

3.排列组合

C(n,m)=n!/(nm)!m!C(n,m)=n!/(n-m)!*m! 用一个数组存储10610^6以内的阶乘,再利用公式进行运算结果。注意的是我们计算的时候出现了取余mod,这会导致不能直接进行除法运算,我们需要将分子与分母的逆元相乘即可。
费马定理:aap2%p=1a*a^{p-2}\%p=1
根据上面证明,可以得出ap2%pa^{p-2}\%paa的逆元

const int N = 1e6 + 20;
long long int da[N], db[N];
long long C(long long n, long long m)
{
    if (n <= m)
        return 1;
    return da[n] * db[n - m] % mod * db[m] % mod;
}

main函数内代码

    da[0] = 1, db[0] = 1;
    for (int i = 1; i <= N; i++)
    {
        da[i] = da[i - 1] * i % mod;
        db[i] = db[i - 1] * qpow(i, mod - 2) % mod;
    }

容斥原理及二项式反演

容斥原理公式:
A1A2A3...An=1inAi1ijnAiAj+1ijknAiAjAk...+(1)n1A1A2A3...AnA_1\cup A_2\cup A_3\cup... A_n=\sum_{1\leq i\leq n}A_i-\sum_{1\leq i\leq j\leq n}A_i\cap A_j+\sum_{1\leq i\leq j\leq k\leq n}A_i\cap Aj\cap A_k-...+(-1)^{n-1}*A_1\cap A_2\cap A_3\cap...A_n
\Rightarrow k=1n(1)k11ij...knAiAj...Ak\sum_{k=1} ^{n} (-1)^{k-1}\sum_{1\leq i\leq j\leq...\leq k\leq n}A_i\cap A_j\cap ...\cap A_k
证明:定义一个元素被m个元素所共有
k=1m(1)k1C(m,k)=C(m,1)C(m,2)+C(m,3)...+(1)m1C(m,m)\sum_{k=1} ^m (-1)^{k-1}C(m,k)=C(m,1)-C(m,2)+C(m,3)-...+(-1)^{m-1}*C(m,m)
\Rightarrow C(m,0)C(m,0)+C(m,1)C(m,2)+C(m,3)...+(1)k1C(m,m)C(m,0)-C(m,0)+C(m,1)-C(m,2)+C(m,3)-...+(-1)^{k-1}C(m,m)
\Rightarrow 1k=0n(1)kC(m,k)1-\sum_{k=0} ^n (-1)^{k}*C(m,k)
\Rightarrow 1(11)m=11-(1-1)^m=1
这说明右式的每个元素都仅仅算了一次
二项式反演:
定义f(n)f(n)为任意n个集合的交集的元素个数,g(n)g(n)为任意n个集合的补集的交集的元素个数。
特别的定义f(0)=U,g(0)=UUf(0)=U,g(0)=U,U代表全集
A1A2...Ak=A1A2...Ak\overline{A_1\cup A_2\cup...\cup A_k}=\overline{A_{1}}\cap \overline{A_{2}}\cap...\cap\overline{A_{k}}
\Rightarrow UA1A2...Ak=A1A2...AkU-\overline{A_{1}}\cap \overline{A_{2}}\cap...\cap\overline{A_{k}}=A_1\cup A_2\cup...\cup A_k
\Rightarrow A1A2...Ak=UA1A2...Ak\overline{A_{1}}\cap \overline{A_{2}}\cap...\cap\overline{A_{k}}=U-A_1\cup A_2\cup...\cup A_k
g(n)=Uk=1n(1)k11ij...knAiAj...Akg(n)=U-\sum_{k=1} ^{n} (-1)^{k-1}\sum_{1\leq i\leq j\leq...\leq k\leq n}A_i\cap A_j\cap ...\cap A_k
容斥原理公式转变(细看容斥原理证明)
\Rightarrow g(n)=Uk=1n(1)k1C(n,k)f(k)g(n)=U-\sum_{k=1} ^n (-1)^{k-1}*C(n,k)*f(k)
又因为f(0)=U,f(0)=U, \Rightarrow g(n)=k=0n(1)kC(n,k)f(k)g(n)=\sum_{k=0} ^n (-1)^{k}*C(n,k)*f(k)
如果我们把补集与原集合进行颠倒,公式仍然成立。
即:f(n)=k=0n(1)kC(n,k)g(k)f(n)=\sum_{k=0} ^n (-1)^{k}*C(n,k)*g(k)
通用公式:f(n)=k=inC(n,k)g(k)f(n)=\sum_{k=i} ^nC(n,k)*g(k) \Rightarrow g(n)=k=in(1)nkc(n,k)f(k)g(n)=\sum_{k=i} ^n(-1)^{n-k}*c(n,k)*f(k)

题解

用不超过k种颜色涂满n个盒子,要求相邻的盒子颜色不同,方案数为:xk=k(k1)n1x_k=k*(k-1)^{n-1}
从k种颜色种选择i种颜色涂满n个盒子,要求相邻的盒子颜色不同,方案数最多为:yk=C(k,i)i(i1)n1y_k=C(k,i)*i*(i-1)^{n-1},这里面一定有重复的。比如我们提前挑选出1,2这两种颜色,然后再从剩下的颜色中随机选一种颜色,有k-2种可能,但是这些可能的情况中,只用1,2颜色涂色的情况都被算过。
根据题意可以写出:
定义tkt_k表示恰好用k种颜色涂满n个盒子,相邻盒子颜色不同的方案数
xn=k=1nC(n,k)tk=C(n,1)t1+C(n,2)t2+...C(n,n)tnx_n=\sum_{k=1} ^nC(n,k)*t_k= C(n,1)*t_1+C(n,2)*t_2+...C(n,n)*t_n
根据二项式反演,得:tn=k=1n(1)nkxkt_n=\sum_{k=1} ^n (-1)^{n-k}*x_k
然后共有m种颜色,挑出k种颜色涂满n个盒子的方案数即为:
ans=C(m,k)tnans=C(m,k)*t_n
注:m的数据范围高达10910^9,所以使用公式:m(m1)(m2)...(mn+1)/n!m*(m-1)*(m-2)*...*(m-n+1)/n!
AC代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
using ull = unsigned long long int;
using pll = pair<ll, ll>;
const ll mod = 1e9 + 7;
long long qpow(long long n, long long m)
{
    if (n == 0 || n == 1)
        return n;
    long long ans = 1;
    while (m)
    {
        if (m & 1)
            ans = ans * n % mod;
        n = n * n % mod;
        m >>= 1;
    }
    return ans % mod;
}
const int N = 1e6 + 20;
long long int da[N], db[N];
//da表示阶乘,db表示逆元
long long C(long long n, long long m)
{
    if (n <= m)
        return 1;
    return da[n] * db[n - m] % mod * db[m] % mod;
}
void dilingtian()
{
    long long n, m, k;
    cin >> n >> m >> k;
  //特例,只有一个盒子的时候
    if (n == 1)
    {
        if (k == 1)
            cout << m << endl;
        else
            cout << 0 << endl;
        return;
    }
    long long x = 1;
    long long ans = 0;
    for (long long i = k; i >= 1; i--)
      //根据公式发现,第k个元素一定为正,所以反向计算
    {
        ans = (ans + x * C(k, i) * i % mod * qpow(i - 1, n - 1) % mod + mod) % mod;
        x *= -1;
    }
    for (ll i = m - k + 1; i <= m; i++)
        ans = ans * i % mod;
    ans = ans * db[k] % mod;
    cout << (ans + mod) % mod << endl;
}
int main(void)
{
    da[0] = 1, db[0] = 1;
    for (int i = 1; i <= N; i++)
    {
        da[i] = da[i - 1] * i % mod;
        db[i] = db[i - 1] * qpow(i, mod - 2) % mod;
    }
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--)
        dilingtian();
    return 0;
}
全部评论

相关推荐

Cassifa:发的字比你都多的一律视为骗子或者想白嫖压榨实习生的
点赞 评论 收藏
分享
02-17 20:43
西北大学 Java
醉蟀:别浪费时间。老板是一个想入行互联网的新人。去年6 7月boss上面看到的。他把所有人都拉到一个微信群,然后一个一个面,自己也在学技术。公司就是一个小区里面租的两间房。都没有买电脑啥的。
点赞 评论 收藏
分享
评论
2
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务