[bzoj2339][HNOI2011]卡农

一道伤脑筋好题……感觉要数学非常好才能做,至于我本人只能看一眼题解orz了……
代码核心部分并不长:


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define MAXM 1000010
const int MOD = 100000007;
const int inf = 1<<30;
const double eps = 1e-8;
typedef long long ll;
#define rep(I, S, T) for (int I = S; I <= T; I ++)
#define rst(ARR) memset(ARR, 0, sizeof(ARR))
#define Mx(A, B) ((A)>(B)?(A):(B))
#define Mn(A, B) ((A)<(B)?(A):(B))
int g[MAXM];
int tot;
int n, m;
int pre[MAXM], invfac[MAXM];
ll pow_q(int a, int x) {
    ll ret = 1;
    for (ll t = a; x; x>>=1, t = t*t%MOD) {
        if (x&1) ret = ret*t%MOD;
    }
    return ret;
}
ll inline inv(int a) {
    return pow_q(a, MOD-2);
}
void init(){
    pre[0] = 1;
    rep(i, 1, m) pre[i] = (ll)pre[i-1]*(tot-i+1)%MOD;
    invfac[0] = 1;
    rep(i, 1, m) invfac[i] = (ll)invfac[i-1]*i%MOD;
    invfac[m] = inv(invfac[m]);
    for (int i = m-1; i; i--) invfac[i] = (ll)invfac[i+1]*(i+1)%MOD;
}
ll C(int m) {
    return (ll)pre[m]*invfac[m]%MOD;
}
int main()
{
    int T;scanf("%d", &T);
    rep(ttttt, 1, T) {
        scanf("%d%d", &n, &m);
        tot = (pow_q(2, n)+MOD-1)%MOD;
        init();
        g[1] = g[2] = 0;
        rep(i, 3, m) {
            g[i] = (C(i-1) - g[i-1] - (ll)g[i-2]*(tot-i+2)%MOD)%MOD;
            g[i] = (g[i]+MOD)%MOD;
            g[i] = g[i]*inv(i)%MOD;
        }
        printf("%d\n", g[m]);
    }
    return 0;
}

考虑2^n-1个由不同音阶组成的集合,从里面选出元素出现总次数为偶数的n-1个片段
设从集合中选择n个不重复、元素出现总次数为偶数的方案排列数f[n]
(为什么是有序的而不是无序的?我也不知道:后面再说)
假设我们已经任意选好了i-1个片段,那么第i个片段可以确定,尽管可能是空的
也就是说,不考虑意外情况,我们最多可以获得 Am12n1 个片段
(任意选m-1个大部分不构成m-1个的合理方案,但是m个的可能方案能确定了)
但是1.第m个片段会是空的2.存在一个片段与它重复
情况1.去掉第m个,剩下的m-1个与一条m-1个片段的序列一一对应,有 f[m-1]种
情况2.这个就要充分利用题目给出的性质:总次数为偶数。既然
两个片段相同,一起拿掉不会影响总数
这样如果两个方案确定了,其他的m-2个片段又会形成f[m-2]种方案
不过这里方案并不确定,只知道相同的片段不会是m-2中之一
所以f[n-2]乘上 2n1(m2) 是情况二对答案的影响
所以:

f[m]=Am12n1f[m1]f[m2](2n1(m2))

可是上面规定从有序思考 而不是无序的
然而得出答案也很容易:f[m]除以m!即可
关于怎么得出这个式子:
题目的限制比较奇特(出现次数为偶次),不太容易推广 遇到这样没见过的也不大容易去想
但是这道题给我们的启发:1.勇敢地使用补集转化思想 比如这道题,有两种情况重复,许多讨论,却是得出正解的有效做法
2.化无序为有序,建立组合与排列的桥梁

补充:在上式中把f[m]改成无序的,直接排列数改成组合数会怎么样?
你会发现——特么连样例都过不了
为什么呢?枚举一下n=2,m=3时 Cm12n1 的方案把:
记 {1} => 1 {2}=>2 {1,2} => 3
方案:{1, 2}, {1, 3}, {2, 3} 各自加上一个非空元素就构成了合法方案
结果发现:它们构成的是一样的
因为我们之前总是从全集中找片段,即使选的不重复,补充出来的也难以不重复
你说:用补集思想转化呀 我们已经选出来了若干重复的方案,集合大小为|S|,共选了 C|S|1|S|
除一下不就好了

g[m]=Cm12n1g[m1]g[m2](2n1(m2))i

注意:只考虑合法方案的重复,所以g[m-1], g[m-2]两项还是放在里面
写出来递推式之后发现果然两种解法本质上是一样的…… 我的算法还要慢(因为要求逆),当然读者有兴趣可以改成O(n)-O(1)处理1..n的乘法逆元。
还是有启示的:都能做出来,只是角度不同 没有必要固守旧想法
预处理组合数的方法:本题模属于质数,但是很大,求C要进行很多次
但是n是固定不变的,m每次加一
可以预处理:1.n..n-i+1(1<=i<=m)的乘积 2.i<=m的阶乘

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值