Loading

[HNOI2011]卡农

portal

Solution

感觉这题非常神仙。

首先是转化一下这个神必的题意(话说这个音阶是什么鬼啊……)。你考虑这样一件事,就是说:这题实际上就是让我们求:在集合 \(S={1,2,\dots,n}\) 中选取 \(m\) 个子集,满足以下条件:

  1. 没有一个子集是空集
  2. 没有两个子集相同
  3. 每一种元素出现次数为偶数

看完还是完全不会,瓶颈是在出现次数上。一直想不到这个出现次数怎么数,看了题解豁然开朗。

启示:当某个东西在数的时候特别难搞,我们考虑放弃其它所有条件,以其独尊,然后利用容斥减掉不合法的东西。
启示:当题目中有有序或者无序的限制的时候,可以考虑互相转化,怎么方便怎么来。

放到这题来,首先把集合变成有序地选择子集。然后我们考虑直接维护第三点条件,不妨设 \(f_i\) 表示有序选取 \(i\) 个子集的方案数,那么只考虑第三点,发现如果前 \(i-1\) 个子集确定,为了满足第三条,最后一个也相应地确定了。在尽可能不破坏前两条的情况下,这样的方案数是 \(A_{2^n-1}^{i-1}\)。我们考虑这样一件事,就是说,这个 \(2^n-1\) 十分地大,但是发现是不变的,把式子写出来可以发现,这是可以递推求的。

然后来考虑减掉不符合前面两条的方案数。考虑第一条,发现我是在 \(2^n-1\) 中,也就是剔除了空集中,选取前 \(i-1\) 个子集的,所以只有可能最后一个为空集。然后我们考虑这样一件事,就是说,我们把 \(i\) 抠掉,实际上就是 \(f_{i-1}\) 种。

然后考虑来减掉不符合第二条的方案数,你考虑这样一件事,就是说,有且仅有可能最后一个与前面某一个相同。然后你考虑这样一件事,就是说,如果我们把两个相同刨掉,那么剩下的是合法的,其方案数就是 \(f_{i-2}\)。然后我们再考虑这样一件事,就是说,还需要考虑是那个集合与最后一个相同,以及这个相同的子集长什么样子。发现前者的方案数是 \(i-1\),后者需要与剩下的 \(i-2\) 个子集不相同,所以是 \((i-1)\times (2^n-1-(i-2))=(i-1)\times (2^n-i+1)\)

所以!你考虑这样一件事,就是说,我们直接求出了递推式,就是说:

\[f_i=A_{2^n-1}^{i-1}-f_{i-1}-(i-1)\times (2^n-i+1)\times f_{i-2} \]

然后你考虑这样一件事,就是说,我们做完了。

Code

// Problem: P3214 [HNOI2011]卡农
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3214
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// Author: ZCETHAN
// Time: 2021-11-10 21:42:47

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=1e6+10;
const ll MOD=1e8+7;
ll ksm(ll a,ll p){
	ll ret=1;while(p){
		if(p&1) ret=ret*a%MOD;
		a=a*a%MOD; p>>=1;
	}return ret;
}ll inv(ll x){return ksm(x,MOD-2);}
ll A[MAXN],f[MAXN];
int main()
{
	int n,m;A[1]=1;f[0]=1;
	scanf("%d%d",&n,&m);
	ll pw=ksm(2,n);
	for(int i=2;i<=m;i++) A[i]=A[i-1]*(pw-i+1)%MOD;
	ll facm=1;
	for(int i=1;i<=m;i++){
		ll les=f[i-1];facm=facm*i%MOD;
		if(i>1) les=(les+f[i-2]*(i-1)%MOD*((pw-i+1)%MOD+MOD)%MOD)%MOD;
		f[i]=A[i]-les;f[i]=(f[i]%MOD+MOD)%MOD;
	}
	printf("%lld\n",f[m]*inv(facm)%MOD);
	return 0;
}
posted @ 2021-11-10 22:14  ZCETHAN  阅读(28)  评论(0编辑  收藏  举报