Atcoder 171_f K.Strivore(有技巧的组合数学)

Atcoder 171_f K.Strivore(有技巧的组合数学)

传送门

题目大意

给一个长度为m字符串,问插入n个字母后可以形成多少种不同的字符串。

解题思路

  一种错误的解法是把给定的字符串中的每个字符看成隔板,然后利用插空法计算有多少种插入方法,最后再乘上26的n次方。这种做法有一个明显的问题就是没有考虑去重。例如:给定字符串abc插入两个字符,首先给定的字符串充当隔板将字符串分隔成“①a②b③c④”。如果我们在②号位插入“ba”得到字符串ababc,同时我们在③号位置插入“ab”也得到字符串ababc。可以发现这两种不同的插入方法会得到相同的字符串,应当被算作一种情况,但却算了两次。
  进一步分析造成重复的原因,利用上面的例子ababc,造成重复的原因是因为我们可以把这个字符串第一个b当作所给字符串abc中的隔板b,也可以把ababc中第二个b当作所给字符串abc中的隔板b。这样为了去重,我们强行规定在一个字符串中隔板字母应当尽可能的最晚出现,比如规定abbbc中第三个b是所给字符串abc中的b,aabbc中第二个b是abc中的b。换句话说在隔板后面的一个位置不能插入带有隔板字母的字符串,比如位置②不能插入含有字母a的字符串,位置③不能插入含有字母b的字符串。这样做是不会漏解的,还是上面的例子,假如在三号位插入一个含b的字符串ba,那么我们总可以找到一个满足我们上述规定的插入方法:在②号位插入b在三号位插入a来替代。因此由于②③④号位置上插入的字母不能和位置前面的隔板字母一致所以只有25种插入方法。而一号位置可以任意插入有26种插入方法。考虑枚举一号位置的长度,再对剩下的位置进行插空法即可,细节参考代码。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#define LL long long
const int MAXN = 2e6 + 5;
const int Mod = 1e9 + 7; 
int n, m;
char str[MAXN];
LL fact[MAXN], inv[MAXN]; 

//快速幂 
LL qpow(LL a, int b) {
	LL res = 1;
	for (; b; b >>= 1, a = a * a % Mod)
		if (b & 1) res = a * res % Mod;
	return res;
}

//组合数 
LL C(int n, int m) {
	return fact[n] * inv[n - m] % Mod * inv[m] % Mod;
} 

int main() {
	LL ans = 0;
	scanf("%d%s", &n, str + 1);
	m = strlen(str + 1);
	
	//阶乘 
	fact[0] = 1;
	for (int i = 1; i <= n + m; i++) fact[i] = fact[i - 1] * i % Mod;
	
	//阶乘逆元,inv[i]表示i的阶乘的逆元 
	inv[n + m] = qpow(fact[n + m], Mod - 2);
	for (int i = n + m - 1; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % Mod;
	
	//统计答案 插空法
	/*
	第一步,枚举①号位置的长度位i同时也要加上第一个隔板,再乘上①号位每个元素26种取值
	第二步,剩下m-1快板和n-i个元素进行插空,再乘上每个元素的25种取值
	最后,用乘法原理乘起来即可
	*/
	for (int i = 0; i <= n; i++) 
		ans  = (ans + qpow(26, i) * C(n + m - i - 1, m - 1) % Mod * qpow(25, n - i) % Mod) % Mod;
	printf("%lld\n", ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【問題概要】 縦 $H$ 行、横 $W$ 列のマスがあります。 上から $i$ 行目、左から $j$ 列目のマスを $(i,j)$ とします。 最初、すべてのマスは白色であり、マス $(i,j)$ は $C_{i,j}$ という文字が書かれています。 あなたは、以下の操作を好きな回数だけ行うことができます。 操作: 黒色を塗られたマス $(i,j)$ を選び、以下のいずれかの操作を行う。 (1) $C_{i,j}$ を $1$ 減らす。 (2) $C_{i,j}$ を $1$ 増やす。 ただし、この操作を行う際には、必ずしも $C_{i,j}$ が $0$ 以上である必要はありません。 操作後、すべてのマスが白色になっている場合、操作回数の最小値を求めてください。 【制約】 ・$1 \leq H,W \leq 50$ ・$0 \leq C_{i,j} \leq 10^{9}$ ・$C_{i,j}$ は整数である。 ・少なくとも $1$ つのマスには文字が書かれている。 【入力】 入力は以下の形式で標準入力から与えられる。 $H$ $W$ $C_{1,1}$ $C_{1,2}$ ... $C_{1,W}$ $C_{2,1}$ $C_{2,2}$ ... $C_{2,W}$ ... $C_{H,1}$ $C_{H,2}$ ... $C_{H,W}$ 【出力】 操作回数の最小値を出力せよ。 【入力例】 3 3 3 1 4 1 5 9 2 6 5 【出力例】 2 【入力例】 3 3 1 1 1 1 1 1 1 1 1 【出力例】 0 【入力例】 4 4 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 1 【出力例】 2 【入力例】 5 5 0 0 1 0 0 1 1 1 1 1 0 0 0 0 0 1 1 1 1 1 0 0 1 0 0 【出力例】 0 【解法】 まず、全体の合計を求めます。 次に、全体の合計が $0$ の場合、操作回数は $0$ となります。 全体の合計が $1$ 以上の場合、以下の操作を行います。 1. 全体の合計を $2$ で割り、切り捨てた値を $S$ とします。 2. 全体の合計が奇数の場合、$S$ を $1$ 増やします。 3. 以下の操作を繰り返します。 1. 最大値を取るマスを選び、そのマスの値を $2$ 減らします。 2. 上記操作によって、全体の合計が $S$ 以下になる場合、操作回数を出力して終了します。 上記操作によって、全体の合計が $S$ 以下になることが証明できます。 また、上記操作によって操作回数が最小になることが証明できます。 【コード】
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值