bzoj 1005 prufer编码+排列组合+高精

2 篇文章 0 订阅

题意:n个点,允许任意两点连边,给出某些点最终的度数,求所有满足要求的树的个数

先介绍prufer编码:

(一)将树转成prufer编码

任意一棵n个点的树都会转成长度为(n-2)prufer编码

度数为m的点,在prufer编码中出现的次数为m-1

第i布时,删去叶子节点中标号最小的点及与它相连的边,并把与它相邻的点加入prufer数列

(二)将prufer编码转成树

设{a1,a2,..an-2}为一棵有n个节点的树的Prufer序列,同时新建集合G{1..n},找出G集合中未在Prufer序列中出现过的最小的数,将该点与Prufer序列中首项连一条边,并将该点和Prufer序列首项删除,重复操作n-2次,最后将G集合中剩余的两个点之间连边即可。

然后我们回归题目

我们用d[i]表示i点要求的最终度数

令 tot=sigma(d[i]-1) (d[i]<>-1) ; m=sigma(1) (d[i]=-1)

根据定义,当tot>n-2 或者 当m=0且tot!=n-2 时,是不合法的,无解

根据排列组合可知 ans=C(n-2,tot)*C(tot,d1-1)*C(tot-d1+1,d2-1)*...*C(dn-1,dn-1)*m^(n-2-tot)

化简可得 ans=(n-2)!*m^(n-2-tot)/((n-2-tot)!*(d1-1)!*(d2-1)!*..*(dn-1)!)

然后蒟蒻发现要写高精除果断弃疗...

然后果断%%%hzwer(一下截自黄学长博客)

var
        n,m,tot,x       :longint;
        i,j             :longint;
        vis             :array[0..1010] of boolean;
        prime           :array[0..1010] of longint;
        up,down,mid     :array[0..1010] of longint;
        ans             :array[0..500010] of longint;

procedure pre_do;
var
        i:longint;
begin
   for i:=2 to n do
   begin
      if not vis[i] then
      begin
         inc(prime[0]);
         prime[prime[0]]:=i;
      end;
      for j:=1 to prime[0] do
        if i*prime[j]>n then break else
        begin
           vis[i*prime[j]]:=true;
           if i mod prime[j]=0 then break;
        end;
   end;
end;

procedure calcdown(x:longint);
var
        tt,i:longint;
begin
   for i:=1 to prime[0] do
   begin
      if prime[i]>x then exit;
      tt:=prime[i];
      while tt<=x do
      begin
         inc(down[i],x div tt);
         tt:=tt*prime[i];
      end;
   end;
end;

procedure calcup(x:longint);
var
        tt,i:longint;
begin
   for i:=1 to prime[0] do
   begin
      if prime[i]>x then exit;
      tt:=prime[i];
      while tt<=x do
      begin
         inc(up[i],x div tt);
         tt:=tt*prime[i];
      end;
   end;
end;

procedure cheng(x:longint);
var
        i,tt:longint;
begin
   for i:=1 to ans[0] do ans[i]:=ans[i]*x;
   tt:=0;
   for i:=1 to ans[0] do
   begin
      inc(ans[i],tt);
      tt:=ans[i] div 10000;
      ans[i]:=ans[i] mod 10000;
   end;
   while tt<>0 do
   begin
      inc(ans[0]);
      ans[ans[0]]:=tt mod 10000;
      tt:=tt div 10000;
   end;
end;

procedure qc(a,b:longint);
begin
   if b=0 then exit;
   if (b and 1=1) then cheng(a);
   if b=1 then exit;
   qc(a,b>>1);
   qc(a,b>>1);
end;

begin
   read(n);
   pre_do;
   for i:=1 to n do
   begin
      read(x);
      if x=-1 then inc(m) else
      begin
         inc(tot,x-1);
         calcdown(x-1);
      end;
   end;
   //
   if (tot>n-2) or ((m=0) and (tot<>n-2)) then
   begin
      writeln(0); exit;
   end;
   //
   if n-2-tot<>0 then calcdown(n-2-tot);
   if n-2<>0 then calcup(n-2);
   ans[0]:=1; ans[1]:=1;
   for i:=1 to prime[0] do mid[i]:=up[i]-down[i];
   for i:=1 to prime[0] do
     if mid[i]>0 then qc(prime[i],mid[i]);
   qc(m,n-2-tot);
   //
   write(ans[ans[0]]);
   for i:=ans[0]-1 downto 1 do
   begin
      if ans[i]<1000 then write(0);
      if ans[i]<100 then write(0);
      if ans[i]<10 then write(0);
      write(ans[i]);
   end;
   writeln;
end.
——by Eirlys



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值