题目
链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2339
分析
我们先把它变成有序的,最后除一个m!
对于有序的方案数我们考虑补集转换。
首先所有的子集个数应该是2^n-1;
我们定义f[i]为使用i个盘子的方案数。
因为要保证总数是偶数。也就是说如果你确定了i-1个盘子第i个盘子也就确定了
所以总数应该是A(2^n-1,i-1);
这样肯定多算了。具体来说有两部分。
1.如果前i-1个已经合法,那么第i个就是空集。这样肯定不合法。所以要减去f[i-1]
2.如果根据前i-1个确定出来的第i个集合和前面的某一个重复,这样肯定是不合法的。
对于第2种情况。
因为考虑顺序,所以那个和第i个重复的集合有i-1种位置.
对于每种位置。当前已经满足偶数了,去掉两个集合之后还是偶数。
所以剩下其他集合的方案数为f[i-2]。
然后我们需要算出有多少种可能重复的方案
因为我们已经确定了(i-2)个位置,所以方案数为(2^n-1-(i-2));
最后再乘上一个m!关于P的逆元即可。
代码
#include <bits/stdc++.h>
#define N 1000010
#define P 100000007
using namespace std;
long long n,m,p[N],f[N],temp;
long long power(long long a,long long b)
{
long long ans(1);
for (long long i = b; i; i>>=1, (a *= a) %= P)
if (i&1)
(ans *= a) %= P;
return ans;
}
void pre()
{
p[0] = 1;
for (long long i = 1; i <= m; i++)
p[i] = (p[i - 1] * ((temp - i + 1 + P) % P)) % P;
}
int main()
{
scanf("%lld%lld",&n,&m);
temp = power(2,n);
temp--;
if (temp < 0)
temp += P;
pre();
for (long long i = 3; i <= m; i++)
f[i] = ((p[i - 1] - f[i - 1] - f[i - 2] * (i - 1) % P * (temp - (i - 2)) % P) + P) % P;
temp = 1;
for (long long i = 1; i <= m; i++)
(temp *= i) %= P;
(f[m] *= power(temp, P - 2)) %= P;
cout<<f[m];
}

本文介绍了一种通过补集转换解决计数问题的方法,并详细解释了如何计算特定条件下子集的方案数量。针对一个给定的问题,通过定义f[i]为使用i个盘子的方案数,利用递推公式来解决计数问题。

367

被折叠的 条评论
为什么被折叠?



