HDU 6143 Killer Names(容斥原理)

hdu 6143 Killer Names 题目链接

题目

T组数据,每组数据给出n和m。有2个字符串A,B,每个字符串长为n,然后从m个字符中选择一些字符去填充字符串。要求A中出现的字符B不能再出现,即每个字符串只能在A或者B中出现,不能同时出现。然后一个字符串中某个字符可以出现任意次,排列顺序不同算不同种类。然后求最多有几种安排方案。
( T 10 ) , ( 1 n , m 2000 )

思路

  • 先考虑字符串A,我们规定它选中了 x 种字符,也就是说A中刚好 x 种字符至少出现一次。然后此时B数组的选择就很简单,长度为 n ,然后每个位置可以有 m x (不能和A有相同字符)个选择。所以B数组的可能情况就是 ( m x ) n .
  • 然后难点就在于如何求此时A数组的情况总数。枚举 x ,然后我们需要求出A中刚好 x 种字符至少出现一次的种类数。
    1. 先从 m 个字符中选择 x 个,即 C ( m , x )
    2. 之后这个问题转化为可重复选择物品的排列数。这里是这样的,假设各物品是 a 1 , a 2 , . . . a n 个,总和为 x ,那么排列数就是 x ! a 1 ! a 2 ! . . . a n ! .但是这题很明显不能这么算,因为把 x 分解明显是个很困难的问题.
    3. 这里就是主要用了一个容斥原理。公式是这样的:
      C ( x , 0 ) x n C ( x , 1 ) ( x 1 ) n + C ( x , 2 ) ( x 1 ) n + . . . C ( x , x ) 0 n

      解释一下:先选0个不放,然后总共有 x n 种,然后减去1个不放的方案,再加上2个不放的方案数……最后就是每个种类至少放一次的方案数。
llong Repentance(int n, int m)                  //选了m个,n次方
{
    int neg = 1;
    llong tmp = 0;
    for (int i=0; i<=m; i++)
    {
        tmp = (tmp + neg * C[m][i] * f[m - i][n] % mod) % mod;
        tmp = (tmp + mod) % mod;
        neg *= -1;
    }
    return tmp;
}

4.我只知道这个结论,但是不会证明,等我学完容斥再来mark……

  • 然后大概的公式我们已经求解出来了。然后gzp写了一发发现TLE了。大概是枚举x,然后每个x又算容斥就是 O ( n 2 ) 的复杂度。同时思考这里有个快速幂的公式 x n , ( x 1 ) n . . . 以及B数组那里的 ( m x ) n 。这里 C 打表肯定每个人都想得到,然后我第一次见到了给幂次打表的操作……
    上面那个代码的第7行的 f [ m i ] [ n ] 就是 ( m i ) n ,这里打了个表,所以每次只要 O ( 1 ) 取得幂次。

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

typedef long long llong;
const int maxn = 2000 + 10;
const int mod = 1e9 + 7;
llong C[maxn][maxn];
llong f[maxn][maxn];

void Pre_C()
{
    C[0][0] = 1;
    for (int i=1; i<maxn; i++)
    {
        C[i][0] = 1;
        for (int j=1; j<=i; j++)
        {
            C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod;
            //printf("C[%d][%d]=%lld\n", i, j, C[i][j]);
        }
    }
}

void Pre_fastpow()
{
    for (int i=1; i<maxn; i++)
        {
            f[i][1] = i;
            for (int j=2; j<maxn; j++)
            f[i][j] = f[i][j-1] * i % mod;
        }
}

llong Repentance(int n, int m)  //选了m个,n次方
{
    int neg = 1;
    llong tmp = 0;
    for (int i=0; i<=m; i++)
    {
        tmp = (tmp + neg * C[m][i] * f[m - i][n] % mod) % mod;
        tmp = (tmp + mod) % mod;
        neg *= -1;
    }
    return tmp;
}

int main()
{
    Pre_C();
    Pre_fastpow();

    int T;
    scanf("%d", &T);
    while (T--)
    {
        int n, m;
        scanf("%d %d", &n, &m);

        llong ans = 0;
        for (int i=1; i<m; i++)
        {
            ans = (ans + C[m][i] * Repentance(n, i) % mod * f[m - i][n] % mod) % mod;
        }

        printf("%lld\n", ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_35414878/article/details/79683744