zoukankan      html  css  js  c++  java
  • 无根树的计数——prufer序列

    参考博客https://www.cnblogs.com/dirge/p/5503289.html

    (1)prufer数列是一种无根树的编码表示,类似于hash。

    一棵n个节点带编号的无根树,对应唯一串长度为n-2的prufer编码。所以一个n阶完全图的生成树个数就是n^{n-2}

    首先定义无根树中度数为1的节点是叶子节点。

    找到编号最小的叶子并删除,序列中添加与之相连的节点编号,重复执行直到只剩下2个节点。

    (2)prufer序列转化为无根树。

    我们设点集为{1,2...n}。然后我们每次找到点集中没有出现在prufer序列中的最小的点(这一定是这个时刻删除的叶子节点),然后再取出prufer序列中的第一个元素,两个点建边,在将两个点在分别删除。

    重要性质:prufer序列中某个编号出现的次数就是这个编号节点的度数-1。

    很多时候,无根树树的问题都可以转化为求解prufer序列的问题,从而大大简化了问题。

    下面提供了三道例题。

    例题:

    BSOJ 2503 -- 【HNOI2004】树的计数

    Description

      一个有n个结点的树,设它的结点分别为v1, v2, …, vn,已知第i个结点vi的度数为di,问满足这样的条件的不同的树有多少棵。 
      给定n,d1, d2, …, dn,编程需要输出满足d(vi)=di的树的个数。

    Input

      第一行是一个正整数n,表示树有n个结点。第二行有n个数,第i个数表示di,即树的第i个结点的度数。其中1<=n<=150,输入数据保证满足条件的树不超过10^17个。

    Output

      输出满足条件的树有多少棵。

    Sample Input

    4 2 1 2 1

    Sample Output

    2

    Hint

      

    根据“prufer序列中某个编号出现的次数就是这个编号节点的度数-1”这一性质,这道题就变成了给定元素个数的排列问题。

    ans=frac{(n-2)!}{prod d_{i}-1}

    代码:
     

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<set>
    #include<map>
    #include<vector>
    #include<ctime>
    #define ll long long
    
    using namespace std;
    inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}
    
    int n;
    ll fac[155];
    ll ksm(ll t,ll x) {
    	ll ans=1;
    	for(;x;x>>=1,t=t*t)
    		if(x&1) ans*=t;
    	return ans;
    }
    bool vis[150];
    ll pri[150],cnt[150];
    void pre(int n) {
    	for(int i=2;i<=n;i++) {
    		if(!vis[i]) pri[++pri[0]]=i;
    		for(int j=1;j<=pri[0]&&i*pri[j]<=n;j++) {
    			vis[i*pri[j]]=1;
    			if(i%pri[j]==0) break;
    		}
    	}
    }
    void work(ll n,int flag) {
    	for(int i=1;i<=pri[0];i++) {
    		for(int j=pri[i];j<=n;j*=pri[i]) {
    			cnt[i]+=flag*n/j;
    		}
    	}
    }
    int tot;
    ll ans[5000];
    void Cheng(int t) {
    	for(int i=1;i<=ans[0];i++) ans[i]*=t;
    	for(int i=1;i<=ans[0];i++) {
    		ans[i+1]+=ans[i]/10;
    		ans[i]%=10;
    	}
    	while(ans[ans[0]+1]) {
    		ans[0]++;
    		ans[ans[0]+1]+=ans[ans[0]]/10;
    		ans[ans[0]]%=10;
    	}
    }
    
    int main() {
    	n=Get();
    	pre(n);
    	work(n-2,1);
    	int a;
    	for(int i=1;i<=n;i++) {
    		a=Get();
    		if(!a&&n>1) {cout<<0;return 0;}
    		if(a>1) work(a-1,-1);
    		tot+=a;
    	}
    	if(tot!=(n-1)*2) return cout<<0,0;
    	if(n==1) {
    		if(tot) cout<<0;
    		else cout<<1;
    		return 0;
    	}
    	if(n==2) return cout<<1,0;
    	ans[0]=1;
    	ans[1]=1;
    	for(int i=1;i<=pri[0];i++) 
    		for(int j=1;j<=cnt[i];j++) Cheng(pri[i]);
    	for(int i=ans[0];i>=1;i--) cout<<ans[i];
    	return 0;
    }
    

    BSOJ 5553 -- 【模拟试题】wangyurzee的树

    Description

    wangyurzee有n个各不相同的节点,编号从1到n。wangyurzee想在它们之间连n-1条边,从而使它们成为一棵树。 
    可是wangyurzee发现方案数太多了,于是他又给出了m个限制条件,其中第i个限制条件限制了编号为u[i]的节点的度数不能为d[i]。 一个节点的度数,就是指和该节点相关联的边的条数。 这样一来,方案数就减少了,问题也就变得容易了,现在请你告诉wangyurzee连边的方案总数为多少。 答案请对1000000007取模。

    Input

    第一行输入2个整数n(1<=n<=1000000),m(0<=m<=17)分别表示节点个数以及限制个数。 
    第2行到第m+1行描述m个限制条件,第i+1行为2个整数u[i],d[i],表示编号为u[i]的节点度数不能为d[i]。 
    为了方便起见,保证1<=ui<=m。同时保证1<=ui<=n,1<=di<=n-1,保证不会有两条完全相同的限制。

    Output

    输出一行一个整数表示答案。

    Sample Input

    3 1 1 2

    Sample Output

    2

    显然要容斥。我们就用{总的方案数}-{不满足一个条件的方案数}+{不满足两个条件的方案数}-...

    注意有个坑点,就是如果我们枚举的两个条件的u相同,那么方案数直接为0。

    然后就是如何计数的问题。假设我们已经定了k个点的度数d[1],d[2]...d[k]。设res=n-2-sum _{1<=i<=k}d[i]-1。那么方案数就是C_{n-2}^{d[1]-1}cdot C_{n-2-(d[1]-1)}^{d[2]-1}...cdot C_{tot+d[k]-1}^{d[k]-1}cdot (n-k)^{res}

    代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<set>
    #include<map>
    #include<vector>
    #include<ctime>
    #define ll long long 
    #define N 1000005
    #define mod 1000000007ll
    
    using namespace std;
    inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}
    
    int n,m;
    int u[20],d[20];
    vector<int>st[N];
    ll fac[N],ans;
    ll ksm(ll t,ll x) {
    	ll ans=1;
    	for(;x;x>>=1,t=t*t%mod)
    		if(x&1) ans=ans*t%mod;
    	return ans;
    }
    ll C(int n,int m) {return fac[n]*ksm(fac[m],mod-2)%mod*ksm(fac[n-m],mod-2)%mod;}
    bool vis[N];
    void dfs(int v,int flag,int n,int res,ll tot) {
    	if(v>m) {
    		(ans+=flag*tot*ksm(res,n)%mod+mod)%=mod;
    		return ;
    	}
    	dfs(v+1,flag,n,res,tot);
    	if(n>=d[v]&&!vis[u[v]]) {
    		vis[u[v]]=1;
    		dfs(v+1,-flag,n-d[v],res-1,tot*C(n,d[v])%mod);
    		vis[u[v]]=0;
    	}
    }
    int main() {
    	n=Get(),m=Get();
    	int a;
    	for(int i=1;i<=m;i++) {
    		u[i]=Get();d[i]=Get()-1;
    	}
    	fac[0]=1;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
    	dfs(1,1,n-2,n,1);
    	cout<<ans;
    	return 0;
    }
    

     

    BSOJ 5445 -- 【2018雅礼】树

    Description

      有n个点,第i个点的限制为度数不能超过ai。
      现在对于每一个s(1<=s<=n),问从这n个点中选出s个点组成有标号无根树的方案数。

    Input

      第一行一个整数表示n。
      第二行n个整数a1~an。

    Output

      输出仅一行n个整数,第i个整数表示s=i时的答案。

    Sample Input

    3 2 2 1

    Sample Output

    3 3 2

    Hint

    【数据范围】
      对于20%的数据,n≤6。
      对于60%的数据,n≤50。
      对于100%的数据,n≤100。

    我们说过,处理无根树计数的问题可以转化为prufer序列的计数问题。

    我们设f[i][j]长度为i的prufer序列,用了j个点的方案数。考虑新增一个点v,我们假设它的度数为k(1<=k<=a[v]),然后就可以得到转移方程f[i+k-1][j+1]+=f[i][j]cdot C_{i+k-1}^{k-1}

    最后特判s=1时,答案是n,s>=2是,答案是f[s-2][s]

    代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<set>
    #include<map>
    #include<vector>
    #include<ctime>
    #define ll long long
    #define mod 1004535809ll
    #define N 105
    
    using namespace std;
    inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}
    
    int n;
    ll f[N][N],g[N][N],c[N][N],w[N];
    int main() {
    	n=Get();
    	c[0][0]=1;
    	for(int i=1;i<=n;i++)
    		for(int j=0;j<=i;j++)
    			c[i][j]=(!j||i==j)?1:(c[i-1][j-1]+c[i-1][j])%mod;
    	f[0][0]=1;
    	for(int i=1;i<=n;i++) {
    		w[i]=Get();
    		memcpy(g,f,sizeof(f));
    		for(int j=0;j<n;j++) {
    			for(int k=0;k<n;k++) {
    				if(!f[j][k]) continue ;
    				for(int q=0;q<w[i]&&q+j<=n-2;q++) {
    					(g[q+j][k+1]+=f[j][k]*c[q+j][q]%mod)%=mod;
    				}
    			}
    		}
    		memcpy(f,g,sizeof(f));
    	}
    	cout<<n<<" ";
    	for(int i=2;i<=n;i++) cout<<f[i-2][i]<<" ";
    	return 0;
    }
    
  • 相关阅读:
    苹果手机页面高度不够会导致下面fixed的按钮看不到了
    IOS系统倒计时直接到结束的问题解决
    TP6集成gatewayworker报错解决
    easywechat实现公众号支付jsapi支付
    Linux 下没有conio.h 的解决方法
    程序跳转语句
    循环控制语句
    arduino入门实践之驱动LCD12864
    Manjaro 安装MariaDB
    Arduino入门实践之人体红外感应模块
  • 原文地址:https://www.cnblogs.com/hchhch233/p/9735816.html
Copyright © 2011-2022 走看看