【LOJ6072】苹果树【折半搜索】【矩阵树定理】【二项式反演】

题意:有好坏两种点共 n n n 个,每个好点有权值,把这 n n n 个点连成一棵树,一个好点为有用的当且仅当它至少与一个好点相邻,求所有有用的点的权值和不超过 l i m lim lim 的方案数。

n ≤ 40 n\leq 40 n40

这题网上的容斥方法基本都是假的……

发现至少与一个好点相邻不好处理,但只能与坏点相邻比较方便,所以大概是个容斥。

设有 m m m 个好点, f ( k ) f(k) f(k) 表示钦定 k k k 个点,有用的点只能在这 k k k 个当中选,相当于钦定其他 m − k m-k mk 个是没用的。(以下简称“可以有用”。) g ( k ) g(k) g(k) 表示恰好有 k k k 个有用的点。

那么有

f ( k ) = ∑ i = 0 k ( k i ) g ( i ) f(k)=\sum_{i=0}^k\binom{k}{i}g(i) f(k)=i=0k(ik)g(i)

钦定 k k k 个可以有用的点后,把所有好点和坏点连边,有用的点和坏点内部连边,就可以算出 f f f

然后你会发现你假了。

第一,你算矩阵树只能钦定固定的 k k k 个可以有用,而 f f f 的定义是任意钦定,钦定这个动作本身的方案是算在其中的。

第二,因为这 k k k 个是不固定的,你算出了 g g g 也没法算答案……

所以必须要改下定义。

定义 f ( k ) f(k) f(k)已经确定了 k k k 个点可以有用的连边方案。也就是具体是哪 k k k 个点已经帮你钦定好了,你只需要管连边的方案。

g ( k ) g(k) g(k)已经确定恰好有 k k k 个点有用的连边方案,具体含义同上。

先不管权值的事情,对于一个钦定可以有用的方案,建出图后考虑它的一棵生成树,把钦定的 k k k 个点中全部连的坏点的拿出来,假设有 i i i 个,就对应了一种 g ( i ) g(i) g(i) 的方案。

所以式子是一样的

f ( k ) = ∑ i = 0 k ( k i ) g ( i ) f(k)=\sum_{i=0}^k\binom{k}{i}g(i) f(k)=i=0k(ik)g(i)

二项式反演

g ( k ) = ∑ i = 0 k ( − 1 ) k − i ( k i ) f ( i ) g(k)=\sum_{i=0}^k(-1)^{k-i}\binom{k}{i}f(i) g(k)=i=0k(1)ki(ik)f(i)

f f f 因为是矩阵树算的,已经钦定好了。对于所有 g ( k ) g(k) g(k),乘上钦定本身的方案的和就是答案。钦定本身可以折半搜索+two pointer算出。

复杂度 O ( 2 n / 2 + n 4 ) O(2^{n/2}+n^4) O(2n/2+n4)

顺带一提,如果是钦定没用的点,式子是长这样的

f ( k ) = ∑ i = k m ( m − k i − k ) g ( i ) f(k)=\sum_{i=k}^m\binom{m-k}{i-k}g(i) f(k)=i=km(ikmk)g(i)

而不是

f ( k ) = ∑ i = k m ( i k ) g ( i ) f(k)=\sum_{i=k}^m\binom{i}{k}g(i) f(k)=i=km(ki)g(i)

原因同上

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
const int MOD=1e9+7;
inline int add(const int& x,const int& y){return x+y>=MOD? x+y-MOD:x+y;}
inline int qpow(int a,int p)
{
	int ans=1;
	while (p)
	{
		if (p&1) ans=(ll)ans*a%MOD;
		a=(ll)a*a%MOD,p>>=1;
	}
	return ans;
}
int C[45][45],n,lim,a[45],cnt[45],f[45];
vector<int> L[45],R[45];
void dfs(vector<int>* v,int cur,int pos,int k,int sum)
{
	if (sum>lim) return;
	if (cur>pos) return v[k].push_back(sum);
	dfs(v,cur+1,pos,k,sum);
	dfs(v,cur+1,pos,k+1,sum+a[cur]);
}
inline int calc(const vector<int>& a,const vector<int>& b)
{
	int pos=b.size(),ans=0;
	for (int i=0;i<(int)a.size();i++)
	{
		while (pos&&a[i]+b[pos-1]>lim) --pos;
		ans=(ans+pos)%MOD;
	}
	return ans;
}
int g[45][45];
inline int det(int n)
{
	int ans=1;
	for (int i=1;i<n;i++) for (int j=1;j<n;j++) (g[i][j]<0)&&(g[i][j]+=MOD);
	for (int i=1;i<n;i++)
	{
		int pos=i;
		for (;!g[pos][i]&&pos<n;++pos);
		if (pos==n) return 0;
		if (pos>i) swap(g[i],g[pos]),ans=MOD-ans;
		ans=(ll)ans*g[i][i]%MOD;
		for (int j=i+1;j<n;j++)
		{
			int t=(ll)g[j][i]*qpow(g[i][i],MOD-2)%MOD;
			for (int k=i;k<n;k++) g[j][k]=(g[j][k]-(ll)t*g[i][k]%MOD+MOD)%MOD;
		}
	}
	return ans;
}
int main()
{
	scanf("%d%d",&n,&lim);
	C[0][0]=1;
	for (int i=1;i<=n;i++)
	{
		C[i][0]=1;
		for (int j=1;j<=i;j++) C[i][j]=add(C[i-1][j-1],C[i-1][j]);
	}
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	int bad=0;
	for (;a[bad+1]==-1;++bad);
	int mid=(bad+1+n)>>1;
	dfs(L,bad+1,mid,0,0),dfs(R,mid+1,n,0,0);
	int m=n-bad;
	for (int i=0;i<=m;i++) sort(L[i].begin(),L[i].end()),sort(R[i].begin(),R[i].end());
	for (int k=0;k<=m;k++)
	{
		for (int i=0;i<=k;i++) cnt[k]=add(cnt[k],calc(L[i],R[k-i]));
		memset(g,0,sizeof(g));
		for (int i=1;i<=bad;i++)
			for (int j=1;j<i;j++)
			{
				++g[i][i],++g[j][j];
				--g[i][j],--g[j][i];
			}
		for (int i=bad+1;i<=bad+k;i++)
			for (int j=1;j<i;j++)
			{
				++g[i][i],++g[j][j];
				--g[i][j],--g[j][i];
			} 
		for (int i=bad+k+1;i<=n;i++)
			for (int j=1;j<=bad;j++)
			{
				++g[i][i],++g[j][j];
				--g[i][j],--g[j][i];	
			}
		f[k]=det(n);
	}
	int sum=0;
	for (int k=0;k<=m;k++)
	{
		int ans=0;
		for (int i=0;i<=k;i++) ans=(ans+(((i-k)&1)? -1ll:1ll)*C[k][i]*f[i])%MOD;
//		cerr<<ans<<'\n';
		sum=(sum+(ll)cnt[k]*ans)%MOD;
	}
	printf("%d\n",(sum+MOD)%MOD);
	return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值