[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.
时间: 2024-07-29 16:42:37

[BZOJ1005]Prufer数列+排列组合的相关文章

BZOJ 1005 明明的烦恼(Prufer数列)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1005 题意:给出一棵树的某些节点的度数d,有些未给.问满足这个条件的树有多少种? 思路:(1)Prufer 数列是无根树的一种数列.由一棵树可以构造出一个Prufer数列,Prufer数列可转化为原来的树.由树生成Prufer的一种简单方法是每次找出标 号最小的叶子节点将其父节点添加到Prufer数列并将该叶子节点删除.直到最后只剩下两个节点时结束.比如下面的这个树按照我们刚才的方法生

PHP_字典序法获得排列组合

前段时间一次聚会闲聊时聊到一个问题,就是给你一排数组,例如1,2,3,4,5,如何能高效的获取上述数列的所有排列组合,正巧没事,研究了一下,一开始以为是个很简单的问题,就直接开始写代码了,后来发现怎么循环也不理想,基本上都有一些不必要的消耗,百度一下看到一个不错的算法,字典序法,顺便学习一下,然后记录之. 摘一段算法思想: 设P是[1,n]的一个全排列. P=P1P2-Pn=P1P2-Pj-1PjPj+1-Pk-1PkPk+1-Pn , j=max{i|Pi<Pi+1}, k=max{i|Pi>

排列组合之全排列

求出一个数列的全排列? 我相信你会遇到这种问题! 交给你一个简单的方法,那就是c++的库函数 next_permutation(a,a+n); #include<iostream> #include<algorithm> using namespace std; int main() { int a[4]={1,2,3,4}; do{ cout<<a[0]<<" "<<a[1]<<" "<

C++写一个排列组合小程序

今天突然想到一个问题,有时候,针对同一个事件有多种反映,特别是游戏AI当中,这种情况下需要采取最适合的方案,哪种方案最适合,可以将每种方案的结果或影响都计算一遍,从而选择最合适的.最基本就是一个排列组合方法,将各种方案都组合出来.于是写了一个基本的N个数排列组合小程序! 开发工具:Visual Studio 2012 CTestPermutation::~CTestPermutation() { cout<<">>>>>>>>>&

排列组合的实现

数据库环境:SQL SERVER2008R2 先说一下需求:实现1,2,3的排列组合,即123,132,213,231,312,321. 哈哈,你没看错,需求这是这么短短的一句话. 我想到了2个方法,都可以实现需求.下面我分别介绍这2种实现方法. 方法一:建一个表xx,往xx表中插入1,2,3自然数列,简单起见,把自然数改成字符串, 然后xx表和xx表进行FULL JOIN(全外连接)得到结果集tmp,结果集tmp再和xx表进行LEFT JOIN(左连接), 关联的条件是当前xx表的数据不存在结

算法练习:排列组合之子集合

问题描述 输入一个含有不同数字的序列,输出其所有子集合(含空集).要求:1)集合里元素有序排列:2)输出结果不含有重复集合 举例 输入序列{3,1,2} 输出:{},{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3} 问题分析 可以使用排列组合问题求解的第一种方法:分期摊还.初始化时,结果集合里含有一个空集.当扫描数列时,保留原有集合,同时将当前元素插入现有的所在集合中,从而形成新的集合.详见后面代码的GetSubSetsAmortized函数. 也可以使用第二种方法:f

hdu1521 排列组合(指数型母函数)

题意: 有n种物品,并且知道每种物品的数量ki.要求从中选出m件物品的排数.         (全题文末) 知识点: 普通母函数 指数型母函数:(用来求解多重集的排列问题) n个元素,其中a1,a2,····,an互不相同,进行全排列,可得n!个不同的排列. 若其中某一元素ai重复了ni次,全排列出来必有重复元素,其中真正不同的排列数应为 ,即其重复度为ni! 同理a1重复了n1次,a2重复了n2次,····,ak重复了nk次,n1+n2+····+nk=n. 对于这样的n个元素进行全排列,可得

洛谷P1246编码问题-排列组合,分类讨论

编码问题 题意就是a,b,c.....ab.....编码,给你一个字符串,输出这是第几个: 这里可以用暴力枚举,但也可以用组合数学的高超知识: 既然这样我就说一下排列组合的方法,如果要弄一个 各位数字递增的三位数,只需要在一个有序数列里面取三个数字,此时就无需关注顺序,因为顺序只能是升序的.比如0 1 2 3 4 5 6 7 8 9.取得9 5 8 那么他的顺序就只能是589.总数就是C(x,y),x代表位数,y代表可供选择的数的长度, 就像例子中是c(3,10).对于字母排列,道理也是一样.只

2041-超级楼梯(斐波那契)/(排列组合)

///1.斐波那契数列 #include<stdio.h> int main() { int a[41]={0,1,1}; int n,m; for(int i=3;i<=40;i++) { a[i]=a[i-1]+a[i-2]; } scanf("%d",&n); while(n--) { scanf("%d",&m); printf("%d\n",a[m]); } return 0; } ///2.排列组合