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;
}
以下是博客签名,正文无关
本文来自博客园,作者:Jijidawang,转载请注明原文链接:https://www.cnblogs.com/CDOI-24374/p/15813340.html
版权声明:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0)进行许可。看完如果觉得有用请点个赞吧 QwQ