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 x x种字符,也就是说A中刚好 x x x种字符至少出现一次。然后此时B数组的选择就很简单,长度为 n n n,然后每个位置可以有 m − x m-x mx(不能和A有相同字符)个选择。所以B数组的可能情况就是 ( m − x ) n (m-x)^n (mx)n.
  • 然后难点就在于如何求此时A数组的情况总数。枚举 x x x,然后我们需要求出A中刚好 x x x种字符至少出现一次的种类数。
  1. 先从 m m m个字符中选择 x x x个,即 C ( m , x ) C(m, x) C(m,x)
  2. 之后这个问题转化为可重复选择物品的排列数。这里是这样的,假设各物品是 a 1 , a 2 , . . . a n a_1, a_2,...a_n a1,a2,...an个,总和为 x x x,那么排列数就是$\frac {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 C(x, 0)x^n-C(x, 1) (x-1)^n + C(x,2)(x-1)^n+...C(x,x)0^n C(x,0)xnC(x,1)(x1)n+C(x,2)(x1)n+...C(x,x)0n
    解释一下:先选0个不放,然后总共有 x n x^n xn种,然后减去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 ) O(n^2) O(n2)的复杂度。同时思考这里有个快速幂的公式$x^n, (x-1)^n … 以 及 B 数 组 那 里 的 以及B数组那里的 B(m-x)^n 。 这 里 。这里 C 打 表 肯 定 每 个 人 都 想 得 到 , 然 后 我 第 一 次 见 到 了 给 幂 次 打 表 的 操 作 … … 上 面 那 个 代 码 的 第 7 行 的 打表肯定每个人都想得到,然后我第一次见到了给幂次打表的操作…… 上面那个代码的第7行的 7f[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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值