[BZOJ1005]Prufer数列+排列组合
一棵树的Prufer数列
每次在剩下的树中找到标号最小的叶子节点(对于无根树而言即是度数为1的节点),删去。
同时将其父节点(即与其相连的唯一点)加入Prufer数列当中。
一个Prufer数列所对应的树
G集合开始为空集
设当前处理到Prufer数列的第i项,找到G集合中未出现且在Prufer[i..n-2]未出现过的标号最小的节点,设其为u。
将u加入集合G中,并将u与Prufer[i]连一条边。
最后将在G集合中仍未出现的两个点之间连一条边(其中必定有一个是n)。
来思考一下为何可以还原成最初的树
首先我们可以把加入G集合这个操作当成是删去叶子节点的操作。
在G集合中未出现代表这个数还在树上未被删去。
再Prufer[i..n-2]中未出现代表这个节点当前的度数为1,即是叶子节点。
所以找出的u其实就等同于标号最小的叶子节点。
显然,树和Prufer数列一一对应。
即对于一棵树有且仅有一个Prufer数列
对于一个Prufer数列能且仅能还原出唯一的一棵树。
一些性质
每一个Prufer数列有n-2项(n为节点的个数)
对于一个度数为x的节点,它在Prufer数列中出现x-1次
对于满足上述两个条件的Prufer数列(当然每个数不能超过n)必能构成一棵合法的树
有了这个想法便可以开始做这道题。
即求可能的Prufer数列的个数。
转化为一个排列组合问题
对于cnt个有度数限制的节点,其a[i]-1的加和我们设为sum,则要在Prufer数列的n-2个空位中选sum的位置放这cnt个点
对于剩下的n-sum-2个空位可以放任意的没有度数限制的点。
(n-2)!*(n-cnt)n-sum-2/(∏(a[i]-1)!*(n-sum-2)!)
求解上述式子可以用分解质因子的方法,对于阶乘的分解可以采用更快的方法。
最后高精度乘法。
program bzoj1005; const maxn=1010;tt=10000; type arr=array[-1..maxn]of longint; var n,cnt,sum,i,j:longint; a,p,w:array[-1..maxn]of longint; vis:array[-1..maxn]of boolean; ans:arr; ss:string; procedure printf; begin writeln(0); halt; end; procedure build; var i,j:longint; begin fillchar(vis,sizeof(vis),true); p[0]:=0; for i:=2 to maxn do begin if vis[i] then begin inc(p[0]); p[p[0]]:=i; end; for j:=1 to p[0] do begin if i*p[j]>maxn then break; vis[i*p[j]]:=false; if i mod p[j]=0 then break; end; end; end; procedure work(x,y:longint); var i,xx,tot:longint; begin xx:=x; for i:=1 to p[0] do begin x:=xx;tot:=0; while x<>0 do begin inc(tot,x div p[i]); x:=x div p[i]; end; inc(w[i],tot*y); end; end; procedure work2(x,y:longint); var i,tot:longint; begin for i:=1 to p[0] do if x mod p[i]=0 then begin tot:=0; while x mod p[i]=0 do begin inc(tot);x:=x div p[i]; end; inc(w[i],tot*y); end; end; function mul(a:arr;b:longint):arr; var c:arr; i:longint; begin fillchar(c,sizeof(c),0); c[0]:=a[0]; for i:=1 to c[0] do begin inc(c[i],a[i]*b); inc(c[i+1],c[i] div tt); c[i]:=c[i] mod tt; end; if c[c[0]+1]<>0 then inc(c[0]); exit(c); end; begin //assign(input,'bzoj1005.in');reset(input); //assign(output,'bzoj1005.out');rewrite(output); readln(n); cnt:=0;sum:=0; for i:=1 to n do begin readln(a[i]); if a[i]>0 then inc(cnt) else if a[i]=0 then printf; if a[i]>0 then inc(sum,a[i]-1); end; fillchar(w,sizeof(w),0); if sum>n-2 then printf; build; work(n-2,1); work(n-2-sum,-1); work2(n-cnt,n-sum-2); for i:=1 to n do if a[i]>0 then work(a[i]-1,-1); ans[0]:=1;ans[1]:=1; for i:=1 to p[0] do for j:=1 to w[i] do ans:=mul(ans,p[i]); write(ans[ans[0]]); for i:=ans[0]-1 downto 1 do begin str(ans[i],ss); for j:=length(ss)+1 to 4 do write(0); write(ans[i]); end; end.