【矩阵树定理+容斥】Topcoder SRM 551 DIV1 Hard SweetFruits (TC12141)

【前言】
吐槽一下TC的交代码,在SRMs的练习模式那里交每提交一次分数上限会少一点,于是根本不知道过没过。在VJ上交才知道过了。

当然可能是我太菜了。
【题目】
Topcoder
给定 n n n个权值,要么非负,要么为 − 1 -1 1,你现在可以用 n − 1 n-1 n1条边连接这些权值得到一棵树。我们称一个权值是有用的当且仅当它它和至少一个非负权值相连且它本身不为负,这棵树的权值为所有有用的权值之和。
求有多少种生成树满足其权值不超过 k k k,答案对 1 0 9 + 7 10^9+7 109+7取模。
n ≤ 40 , k ≤ 1 0 9 n\leq 40,k\leq 10^9 n40,k109

【解题思路】
这个题很有意思啊。

首先一个思路是枚举哪些权值是有用的权值,然后计算合法方案。观察到枚举有用权值个数相同时,合法方案的数量也是一样的,因此可以一起计算。

不妨设 f i f_i fi表示选出 i i i个权值且和不超过 k k k的方案数, g i g_i gi表示有 i i i个权值是有用的生成树个数,那么最后的答案就是 ∑ i = 0 n f i × g i \sum_{i=0}^nf_i\times g_i i=0nfi×gi

对于 f i f_i fi,我们可以通过 meet in the middle \text{meet in the middle} meet in the middle来求出。

对于 g i g_i gi做法还是很高妙的:我们建立一幅新图,前 i i i个点当作有用的权值点,第 i + 1 i+1 i+1到第 i + j i+j i+j个点当作非负的权值点,剩下的 n − i − j n-i-j nij个点当作 − 1 -1 1的点。那么将前 i i i个点两两连边,后 n − i − j n-i-j nij个点两两连边,前 i i i个点和最后 n − i − j n-i-j nij个点两两连边,第 i + 1 i+1 i+1到第 i + j i+j i+j个点和最后 n − i − j n-i-j nij个点两两连边,跑矩阵树定理。这样我们可以求出有用的权值至多为 i i i的方案数,容斥一下就可以得到真正的 g i g_i gi了。

复杂度 O ( n ⋅ 2 n 2 + n 4 ) O(n\cdot 2^{\frac n 2}+n^4) O(n22n+n4),当然前半部分也许不用那个 n n n,这个上界是十分松的。

【参考代码】

#include
#define pb push_back
using namespace std;

const int N=42,mod=1e9+7;

namespace Math
{
	int upm(int x){return x>=mod?x-mod:(x<0?x+mod:x);}
	void up(int &x,int y){x=upm(x+y);}
	int mul(int x,int y){return 1ll*x*y%mod;}
	int qpow(int x,int y){int res=1;for(;y;y>>=1,x=mul(x,x))if(y&1)res=mul(res,x);return res;}
}
using namespace Math;

namespace DreamLolita
{
	int n,cnt,mxv;
	int A[N][N],C[N][N],f[N],g[N];
	vector<int>a,L[N],R[N];
	void dfsl(int now,int lim,int c,int sum)
	{
		if(sum>mxv) return;
		if(now==lim){L[c].pb(sum);return;}
		dfsl(now+1,lim,c,sum);dfsl(now+1,lim,c+1,sum+a[now]);
	}
	void dfsr(int now,int lim,int c,int sum)
	{
		if(sum>mxv) return;
		if(now==lim){R[c].pb(sum);return;}
		dfsr(now+1,lim,c,sum);dfsr(now+1,lim,c+1,sum+a[now]);
	}
	void initf()
	{
		int mid=a.size()/2,len=a.size();
		dfsl(0,mid,0,0);dfsr(mid,len,0,0);
		for(int i=0;i<=mid;++i) sort(L[i].begin(),L[i].end());
		for(int i=0;i<=len-mid;++i) sort(R[i].begin(),R[i].end());
	}
	int calcf(int m)
	{
		int res=0;
		for(int i=0;i<=m;++i)
		{
			if(!L[i].size() || !R[m-i].size()) continue;
			int r=R[m-i].size()-1;
			for(int l=0;l<(int)L[i].size();++l)
			{
				while(~r && L[i][l]+R[m-i][r]>mxv) --r;
				if(!~r) break;
				up(res,r+1);
			}
		}
		return (f[m]=res);
	}
	int det(int n)
	{
		int op=1,res=1;
		for(int i=1;i<n;++i) for(int j=1;j<n;++j) up(A[i][j],0);
		for(int i=1;i<n;++i)
		{
			int x=i;
			for(int j=i;j<n;++j) if(A[j][i]) x=j;
			if(x^i) swap(A[x],A[i]),op^=1;
			res=mul(res,A[i][i]);
			for(int inv=qpow(A[i][i],mod-2),j=1;j<=n;++j) A[i][j]=mul(A[i][j],inv);
			for(int j=i+1;j<n;++j)
			{
				int t=A[j][i];
				for(int k=1;k<n;++k) up(A[j][k],-mul(A[i][k],t));
			}
		}
		return upm((op?res:-res));
	}
	void upmat(int i,int j){--A[i][j],--A[j][i],++A[i][i],++A[j][j];}
	int calcg(int m)
	{
		memset(A,0,sizeof(A));
		for(int i=1;i<=m;++i) for(int j=i+1;j<=m;++j) upmat(i,j);
		for(int i=1;i<=m;++i) for(int j=cnt+1;j<=n;++j) upmat(i,j);
		for(int i=m+1;i<=cnt;++i) for(int j=cnt+1;j<=n;++j) upmat(i,j);
		for(int i=cnt+1;i<=n;++i) for(int j=i+1;j<=n;++j) upmat(i,j);
		int res=det(n);
		for(int i=0;i<m;++i) up(res,-mul(C[m][i],g[i]));
		return (g[m]=res);
	}
}
using namespace DreamLolita;

class SweetFruits
{
public:
	int countTrees(vector<int> vec,int mx)
	{
		n=vec.size();mxv=mx;
		for(int i=0;i<=n;++i) 
		{
			C[i][i]=C[i][0]=1;
			for(int j=1;j<i;++j) C[i][j]=upm(C[i-1][j]+C[i-1][j-1]);
		}
		for(int i=0;i<n;++i) if(~vec[i]) ++cnt,a.pb(vec[i]);
		int ans=0;initf();
		for(int i=0;i<=cnt;++i) up(ans,mul(calcf(i),calcg(i)));
		return ans;
	}
};

/*int main()
{
#ifdef Durant_Lee
	freopen("TC12141.in","r",stdin);
	freopen("TC12141.out","w",stdout);
#endif
	vectortmp;int k,x;scanf("%d",&k);
	for(int i=1;i<=k;++i)  scanf("%d",&x),tmp.pb(x);
	scanf("%d",&x);
	printf("%d\n",(new SweetFruits())->countTrees(tmp,x));
	return 0;
}*/

你可能感兴趣的:(其他-矩阵树定理,数论-容斥原理)