HNOI2011 卡农 题解

真·状压dp

题面

啥比题面

等价于在 S={1,2,,2n1} 中选 m 个数(无序),满足:

  • 两两不同
  • 异或和为 0 .

问方案数,对 108+7 取模 .

题解

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

dpk 表示选了 k 个数的方案数 .

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

于是直接大力选,方案数为 A2n1k1 .

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

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

dpk2(k1)(2n1(k1))

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

dpk=A2n1k1dpk1dpk2(k1)(2n1(k1))

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

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

代码

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 @   yspm  阅读(49)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
😅​
点击右上角即可分享
微信分享提示