题目大意
给定
n
个苹果,对于苹果
现在要用
给定 limit ,问有多少种不同的生成树,满足其权值小于等于 limit 。答案对 109+7 取模。两个生成树是不同的,当且仅当存在一条边 (u,v) ,满足 (u,v) 在一棵生成树中出现,在另外一棵中没有出现。
(n<=40)
题解
首先,我们讲生成树上的点分为三种:1.坏点(-1),2.有用的点,3.没用的点。
那么,先不考虑点的标号和权值,得到的生成树应该满足以下几个性质:
- 没用的点只能和坏点连边,
- 有用的点可以和坏点和除自己之外的有用的点连边,
- 坏点可以和任何一个除自己的之外的点连边。
- 有用的点至少和一个其他有用的点连边。
因为考虑最后一个条件的话无法进行构图(或者很难构图),所以我们先只考虑前三个条件进行构图,然后再在后面处理的过程中考虑容斥。
考虑前三个条件:设图中有
x
个有用的点,
然后,对于每一个组合按照上面的规则连边:坏点和所有除自己以外的点连边,有用的点和除自己之外的有用的点连边。然后就可以用矩阵树定理求出这个组合下的生成树个数,记有i个有用的点的图此种生成树的个数为 G[i] 。
因为上述的做法没有考虑第四个限制条件,所以我们考虑容斥。说明容斥之前,先明确两个数组的定义:
- G[i]: 表示不考虑第四个限制条件,有 i 个有用的点时,不同的生成树的个数;
F[i]: 表示考虑第四个限制条件,有 i 个有用的点时,不同的生成树的个数。
考虑容斥时,在整体中减去不合法的部分。例如,当我们有i个有用的点,且不考虑条件四时,所有这样的生成树中可能有
但是上面的过程没有考虑苹果的权值和标号,那接下来就考虑一下。
对于上面我们处理出来的
F[i]
数组,
F[i]
所表示的含义为当生成树中有
i
个有用的点时,不考虑苹果的标号,结构不同的生成树的个数。如果不考虑题面中对生成树权值和的限制
考虑枚举,枚举每一个非坏点的苹果是否使用,那么直接就可以处理出数组
H[i]
,表示选
i
个苹果作为有用苹果的合法方案数,时间复杂度是
因为
n
<=40,所以可以考虑Meet in middle,就是说,可以把前一半苹果和后一半苹果分开来考虑。这样我们可以处理出一个信息数组,每个元素的含义为“这种方案下权值和为
最后,
太他娘的奇妙了。
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int moder=int(1e9)+7;
inline int les(const int &a,const int &b) {int c=a-b; return c<0?c+moder:c;}
inline int add(const int &a,const int &b) {int c=a+b; return c>=moder?c-moder:c;}
inline int mul(const int &a,const int &b) {long long c=1ll*a*b; return c%moder;}
inline int fpow(int,int);
inline int inv(const int &a) {return fpow(a,moder-2);}
int fpow(int a,int k) {
register int val=1;
for(;k;k>>=1,a=mul(a,a)) if(k&1) val=mul(val,a);
return val;
}
const int maxn=55;
int n,lit;
int f[maxn][maxn];
inline void Add_eage(int x,int y) {
if(!f[x][y]) ++f[x][x], ++f[y][y];
f[x][y]=f[y][x]=-1;
}
//求Kirchhoff矩阵的n-1阶主子式的行列式的绝对值
int Gauss(const int n) {
register int i,j,k, res=1;
for(i=1;i<=n;++i) {
if(!f[i][i]) {
res=-res;
for(j=i+1;j<=n;++j) if(f[j][i]) break;
for(k=1;k<=n;++k) swap(f[i][k],f[j][k]);
}
res=mul(res,f[i][i]);
for(j=i+1;j<=n;++j) {
int ml=mul(f[j][i],inv(f[i][i]));
for(k=i;k<=n;++k) f[j][k]=les(f[j][k],mul(f[i][k],ml));
}
}
return res;
}
//构造Kirchhoff矩阵
void Construct(const int &x,const int &y,const int &z) {
register int i,j;
memset(f,0,sizeof f);
for(i=x+y+1;i<=n;++i)
for(j=1;j<=n;++j) if(i!=j)
Add_eage(i,j);
for(i=1;i<=x;++i)
for(j=1;j<=x;++j) if(i!=j)
Add_eage(i,j);
return;
}
int Fact[maxn], Inv[maxn];
inline int C(const int &n,const int &m) {return mul(Fact[n],mul(Inv[m],Inv[n-m]));}
void Init() {
Fact[0]=Inv[0]=1;
register int i;
for(i=1;i<maxn;++i) Fact[i]=mul(Fact[i-1],i), Inv[i]=inv(Fact[i]);
return;
}
pair<int,int> s1[int(1.5e6)], s2[int(1.5e6)];
int top1, top2;
int pre[int(1.5e6)][maxn];
//Meet in middle
void MiM(int *w,int n,pair<int,int> *sta,int &top) {
register int mask,tmp,num,i;
for(mask=0;mask<(1<<n);++mask) {
long long sum=0;
for(tmp=mask,num=0,i=1;tmp && sum<=lit;tmp>>=1,++i) if(tmp&1 && ~w[i])
sum+=w[i], ++num;
if(sum>lit) continue;
sta[++top]=make_pair(sum,num);
}
return;
}
int c[maxn];
int G[maxn], F[maxn];
int h[maxn];
int main() {
#ifndef ONLINE_JUDGE
freopen("apple.in","r",stdin);
freopen("apple.out","w",stdout);
#endif
register int i,j;
Init();
scanf("%d%d",&n,&lit);
for(i=1;i<=n;++i) scanf("%d",&c[i]);
sort(c+1,c+1+n);
int num=0;
while(!~c[num+1]) num++;
//预处理G[]
for(i=0;i<=n-num;++i) {
Construct(i,n-num-i,num);
G[i]=Gauss(n-1);
}
//预处理F[]
F[0]=G[0];
for(i=1;i<=n-num;++i) {
F[i]=add(F[i],G[i]);
for(j=0;j<i;++j) F[i]=les(F[i],mul(F[j],C(i,j)));
}
int len1=(n-num)>>1, len2=n-num-len1;
MiM(c+num,len1,s1,top1);
sort(s1+1,s1+1+top1);
MiM(c+num+len1,len2,s2,top2);
sort(s2+1,s2+1+top2);
//处理出前缀和数组
for(i=1;i<=top2;++i)
++pre[i][s2[i].second];
for(j=0;j<=n;++j)
for(i=1;i<=top2;++i)
pre[i][j]+=pre[i-1][j];
//利用单调性,尺取法统计h[]
int p1=1, p2=1;
while(p2<=top2 && s1[p1].first+s2[p2].first<=lit) ++p2; --p2;
for(p1=1;p1<=top1;++p1) {
while(p2 && s1[p1].first>lit-s2[p2].first) --p2;
for(i=0;i<=n;++i) h[s1[p1].second+i]=add(h[s1[p1].second+i],pre[p2][i]);
}
int ans=0;
for(i=0;i<=n;++i) ans=add(ans,mul(F[i],h[i]));
printf("%d\n",ans);
return 0;
}