Codeforces 1111D(退背包、排列组合)

要点

  • 优质题解
  • 因为只有某type坏人全部分布在同一撇时,才能一次消灭。所以题目安排完毕后一定是type(x)和type(y)占一半,其余占另一半。
  • 实际情况只有52*52种,则预处理答案
  • 枚举某两种,并连续两次使用退背包得到无排列的方案数,真·答案是有排列的,乘上一个排列数即可,而根据式子,排列数恰好与方案细节无关,是个与(|s|)和全部(cnt[i])有关的定值
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;

string s;
int q, len, x, y;
int cnt[55];
ll f[maxn >> 1], g[maxn >> 1], h[maxn >> 1], Ans[55][55];
ll fac[maxn], finv[maxn];

inline int id(char c) {
	return c >= 'A' && c <= 'Z' ? c - 'A' + 26 : c - 'a';
}

ll ksm(ll a, int b) {
	ll res = 1LL;
	for (; b; b >>= 1) {
		if (b & 1)	res = res * a % mod;
		a = a * a % mod;
	}
	return res;
}

ll pre() {
	fac[0] = 1;
	for (int i = 1; i <= len; i++) {
		fac[i] = fac[i - 1] * i % mod;
	}
	finv[len] = ksm(fac[len], mod - 2);
	for (int i = len - 1; i; --i) {
		finv[i] = finv[i + 1] * (i + 1) % mod;
	}

	ll res = 2LL * fac[len / 2] % mod * fac[len / 2] % mod;
	for (int i = 0; i < 52; i++) {
		if (!cnt[i])	continue;
		res = res * finv[cnt[i]] % mod;
	}
	return res;
}

int main() {
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	cin >> s;
	for (auto i : s) {
		cnt[id(i)]++;
	}
	len = s.length();
	ll base = pre();

	f[0] = 1;
	for (int i = 0; i < 52; i++) {
		if (!cnt[i])	continue;
		for (int j = len / 2; j >= cnt[i]; --j) {
			f[j] = (f[j] + f[j - cnt[i]]) % mod;
		}
	}
	for (int i = 0; i < 52; i++) {
		if (!cnt[i])	continue;
		for (int j = 0; j <= len / 2; j++) {
			if (j < cnt[i])	g[j] = f[j];
			else	g[j] = (f[j] - g[j - cnt[i]] + mod) % mod;
		}
		Ans[i][i] = base * g[len / 2] % mod;
		for (int j = i + 1; j < 52; j++) {
			if (!cnt[j])	continue;
			for (int k = 0; k <= len / 2; k++) {
				if (k < cnt[j])	h[k] = g[k];
				else	h[k] = (g[k] - h[k - cnt[j]] + mod) % mod;
			}
			Ans[i][j] = Ans[j][i] = base * h[len / 2] % mod;
		}
	}

	for (cin >> q; q; q--) {
		cin >> x >> y;
		cout << Ans[id(s[x - 1])][id(s[y - 1])] << '
';
	}
	return 0;
}
原文地址:https://www.cnblogs.com/AlphaWA/p/10829191.html