题意:有好坏两种点共 nnn 个,每个好点有权值,把这 nnn 个点连成一棵树,一个好点为有用的当且仅当它至少与一个好点相邻,求所有有用的点的权值和不超过 limlimlim 的方案数。
n≤40n\leq 40n≤40
这题网上的容斥方法基本都是假的……
发现至少与一个好点相邻不好处理,但只能与坏点相邻比较方便,所以大概是个容斥。
设有 mmm 个好点, f(k)f(k)f(k) 表示钦定 kkk 个点,有用的点只能在这 kkk 个当中选,相当于钦定其他 m−km-km−k 个是没用的。(以下简称“可以有用”。)g(k)g(k)g(k) 表示恰好有 kkk 个有用的点。
那么有
f(k)=∑i=0k(ki)g(i)f(k)=\sum_{i=0}^k\binom{k}{i}g(i)f(k)=i=0∑k(ik)g(i)
钦定 kkk 个可以有用的点后,把所有好点和坏点连边,有用的点和坏点内部连边,就可以算出 fff。
然后你会发现你假了。
第一,你算矩阵树只能钦定固定的 kkk 个可以有用,而 fff 的定义是任意钦定,钦定这个动作本身的方案是算在其中的。
第二,因为这 kkk 个是不固定的,你算出了 ggg 也没法算答案……
所以必须要改下定义。
定义 f(k)f(k)f(k) 为已经确定了 kkk 个点可以有用的连边方案。也就是具体是哪 kkk 个点已经帮你钦定好了,你只需要管连边的方案。
g(k)g(k)g(k) 为已经确定恰好有 kkk 个点有用的连边方案,具体含义同上。
先不管权值的事情,对于一个钦定可以有用的方案,建出图后考虑它的一棵生成树,把钦定的 kkk 个点中全部连的坏点的拿出来,假设有 iii 个,就对应了一种 g(i)g(i)g(i) 的方案。
所以式子是一样的
f(k)=∑i=0k(ki)g(i)f(k)=\sum_{i=0}^k\binom{k}{i}g(i)f(k)=i=0∑k(ik)g(i)
二项式反演
g(k)=∑i=0k(−1)k−i(ki)f(i)g(k)=\sum_{i=0}^k(-1)^{k-i}\binom{k}{i}f(i)g(k)=i=0∑k(−1)k−i(ik)f(i)
fff 因为是矩阵树算的,已经钦定好了。对于所有 g(k)g(k)g(k),乘上钦定本身的方案的和就是答案。钦定本身可以折半搜索+two pointer算出。
复杂度 O(2n/2+n4)O(2^{n/2}+n^4)O(2n/2+n4)
顺带一提,如果是钦定没用的点,式子是长这样的
f(k)=∑i=km(m−ki−k)g(i)f(k)=\sum_{i=k}^m\binom{m-k}{i-k}g(i)f(k)=i=k∑m(i−km−k)g(i)
而不是
f(k)=∑i=km(ik)g(i)f(k)=\sum_{i=k}^m\binom{i}{k}g(i)f(k)=i=k∑m(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;
}