HNOI2011 卡农 题解

真·状压dp

题面

啥比题面

等价于在 \(S=\{1,2,\cdots,2^n-1\}\) 中选 \(m\) 个数(无序),满足:

  • 两两不同
  • 异或和为 \(0\) .

问方案数,对 \(10^8+7\) 取模 .

题解

这个无序是个假的,最后除掉 \(m!\) 就完了,,,

\(dp_{k}\) 表示选了 \(k\) 个数的方案数 .

考虑容斥,显然如果前 \(k-1\) 个数都确定了,则第 \(k\) 个数也就确定了(第 \(k\) 个数为前面数的异或和)

于是直接大力选,方案数为 \(A_{2^n-1}^{k-1}\) .

然后可能违反限制,我们算一下:

  • 前面 \(k-1\) 个数的异或和为 \(0\),此时我们的第 \(k\) 个数为 \(0\) 是不在 \(S\) 中的,所以排掉,方案数 \(dp_{k-1}\) .
  • 存在相同:设 \(k, q\) 两位置存在相同,则丢掉它们俩异或和不变(为 \(0\)),\(i\) 的取值显然有 \(2^n-1-(k-1)\) 种,选 \(q\) 的位置有 \(k-1\) 种,所以总方案数为

\[dp_{k-2}(k-1)(2^n-1-(k-1)) \]

减掉,于是得到最终转移方程 .

\[\large dp_k = A_{2^n-1}^{k-1} - dp_{k-1} - dp_{k-2}(k-1)(2^n-1-(k-1)) \]

因为 \(A\) 的底是不变的,所以可以预处理下降幂,然后可以先算出 \(2^n-1\) 然后转移的时候直接用 .

这样就可以 \(O(1)\) 转移了,总复杂度 \(O(\log n+m)\)(迫真,\(\log n\) 是快速幂友情赠送) .

代码

using namespace std;
typedef long long ll;
const int N = 1e6+500, P = 1e8+7;
int n, m;
ll dpow[N], N2;
ll dp[N];
ll qpow(ll a, ll n)
{
	ll ans = 1;
	while (n)
	{
		if (n&1) ans = ans * a % P;
		a = a * a % P; n >>= 1;
	} return ans;
}
void init()
{
	N2 = qpow(2, n) - 1;
	dpow[0] = 1;
	for (int i=1; i<=m; i++) dpow[i] = dpow[i-1] * (N2-i+1) % P;
}
inline ll inv(const ll& x){return qpow(x, P-2) % P;}
inline ll fac(const int& x){return x ? fac(x-1) * x % P : 1;}
int main()
{
#ifndef ONLINE_JUDGE
	freopen("i.in", "r", stdin);
#endif
	scanf("%d%d", &n, &m); init();
	dp[0] = 1;
	for (int i=2; i<=m; i++)
		dp[i] = ((dpow[i-1] - dp[i-1] + P) % P - dp[i-2] * (i-1) % P * (N2 - (i-2)) % P + P) % P;
	printf("%lld\n", dp[m] * inv(fac(m)) % P);
	return 0;
}
posted @ 2022-01-17 14:27  Jijidawang  阅读(47)  评论(0编辑  收藏  举报
😅​