[DP] [高精度] [Tyvj P2869]血缘关系 (family)

题目描述 Description

我们正在研究妖怪家族的血缘关系。每个妖怪都有相同数量的基因,但是不同的妖怪的基因可能是不同的。我们希望知道任意给定的两个妖怪之间究竟有多少相同的基因。由于基因数量相当庞大,直接检测是行不通的。但是,我们知道妖怪家族的家谱,所以我们可以根据家谱来估算两个妖怪之间相同基因的数量。
妖怪之间的基因继承关系相当简单:如果妖怪 C C C是妖怪 A A A B B B的孩子,则 C C C的任意一个基因只能是继承 A A A B B B的基因,继承 A A A B B B的概率各占 50 % 50\% 50%。所有基因可认为是相互独立的,每个基因的继承关系不受别的基因影响。
现在,我们来定义两个妖怪 X X X Y Y Y的基因相似程度。例如,有一个家族,这个家族中有两个毫无关系(没有相同基因)的妖怪 A A A B B B,及它们的孩子 C C C D D D。那么 C C C D D D相似程度是多少呢?因为 C C C D D D的基因都来自 A A A B B B,从概率来说,各占 50 % 50\% 50%。所以,依概率计算 C C C D D D平均有 50 % 50\% 50%的相同基因, C C C D D D的基因相似程度为 50 % 50\% 50%。需要注意的是,如果 A A A B B B之间存在相同基因的话, C C C D D D的基因相似程度就不再是 50 % 50\% 50%了。
你的任务是写一个程序,对于给定的家谱以及成对出现的妖怪,计算它们之间的基因相似程度。

输入 Input

第一行两个整数 n n n k k k n ( 2 ≤ n ≤ 300 ) n(2≤n≤300) n2n300表示家族中成员数,它们分别用 1 , 2 , … , n 1, 2, …, n 1,2,,n来表示。 k ( 0 ≤ k ≤ n − 2 ) k(0≤k≤n-2) k0kn2表示这个家族中有父母的妖怪数量(其他的妖怪没有父母,它们之间可以认为毫无关系,即没有任何相同基因)。
接下来的 k k k行,每行三个整数 a , b , c a, b, c a,b,c,表示妖怪 a a a是妖怪 b b b和妖怪 c c c的孩子。
然后是一行一个整数 m ( 1 ≤ m ≤ n 2 ) m(1≤m≤n^2) m1mn2,表示需要计算基因相似程度的妖怪对数。
接下来的 m m m行,每行两个整数,表示需要计算基因相似程度的两个妖怪。
你可以认为这里给出的家谱总是合法的。具体来说就是,没有任何的妖怪会成为自己的祖先,并且你也不必担心会存在性别错乱问题。

输出 Output

m m m行。第 k k k行表示第 k k k对妖怪之间的基因相似程度。你必须按百分比输出,有多少精度就输出多少,但不允许出现多余的 0 0 0(注意, 0.001 0.001 0.001的情况应输出 0.1 % 0.1\% 0.1%,而不是 . 1 % .1\% .1%)。具体格式参见样例。

样例输入 Sample Input

7 4
4 1 2
5 2 3
6 4 5
7 5 6
4
1 2
2 6
7 5
3 3

样例输出 Sample Output

0%
50%
81.25%
100%

限制 Limits

见题目
Time Limit : 1 s 1s 1s & Memory Limit : 128 M B 128MB 128MB (在Tyvj上为 64 M B 64MB 64MB)

首先,Tyvj给得 64 M B 64MB 64MB内存C++不够用…换成Pascal就过了…
数组开的一样大啊…
首先这道题我们可爱的学长Slongle写过但是WA了…
可以先看一下这里
看完题我的内心是崩溃的…
妖怪贵圈真乱…
下面是标准解释:
本题是一道概率计算题,但这个概率计算又是建立在Family Tree模型上的。Family Tree是一个有向无环图,有明显的阶段性(辈分关系),而且没有后效性(没有人可以成为自己的祖先),符合动态规划模型的基本条件。因此,应该可以套用类似动态规划的方法来解决。
我们先来明确一下相似程度的计算方法。假设我们要求的是 A A A B B B的相似程度(设为 P ( A , B ) P(A, B) P(A,B)),那么有两种情况是显然的:
(1) A = B : P ( A , B ) = 1 ; A=B:P(A, B)=1; A=B:P(A,B)=1
(2) A A A B B B无相同基因: P ( A , B ) = 0 。 P(A, B)=0。 P(A,B)=0
这是计算其他复杂情况的基础。因为动态规划就是从一些特定的状态(边界条件)出发,分阶段推出其他状态的值的。
再来看一般的情况,设 A 0 A_0 A0 A 1 A_1 A1 A A A的父母。那么,取概率平均情况, A A A拥有 A 0 A_0 A0 A 1 A_1 A1的基因各占一半。假设 A 0 A_0 A0 B B B的相似程度为 P ( A 0 , B ) P(A_0, B) P(A0,B) A 1 A_1 A1 B B B的相似程度为 P ( A 1 , B ) P(A_1, B) P(A1,B),那么 P ( A , B ) P(A, B) P(A,B) P ( A 0 , B ) 和 P ( A 1 , B ) P(A_0, B)和P(A_1, B) P(A0,B)P(A1,B)之间应该是一个什么样的关系呢?很容易猜想到:
P ( A , B ) = ( P ( A 0 , B ) + P ( A 1 , B ) ) / 2 P(A, B)=(P(A_0, B)+P(A_1, B))/2 P(A,B)=(P(A0,B)+P(A1,B))/2
但是,这只是一个猜想。要让它变为一个结论还需要证明:
我们用归纳法来证明。
首先,我们知道,在这个问题中不同基因都是从特定的祖先传下来的,不会出现同一个基因采自不同的祖先的情况(注:这里的祖先是指那些没有父母的妖怪)。所以,如果 A A A B B B有相同的基因,这些基因必然来自同一个祖先。这里,我们最想说明的是,祖先那代是不存在两个人,它们之间不同的基因相同的概率不一样,因为它们相同的概率都是 0 0 0
现在, A A A有祖先 A 0 A_0 A0 A 1 A_1 A1 A 0 A_0 A0 A 1 A_1 A1 B B B的相似程度分别为 P ( A 0 , B ) P(A_0, B) P(A0,B) P ( A 1 , B ) P(A_1, B) P(A1,B)。从祖先一代开始归纳,可由归纳假设 A 0 A_0 A0 B B B A 1 A_1 A1 B B B之间每个基因相同的概率都是一样的,分别都是 P ( A 0 , B ) P(A_0, B) P(A0,B) P ( A 1 , B ) P(A_1, B) P(A1,B) A A A的单个基因,它可能是继承 A 0 A_0 A0的,也可能是继承 A 1 A_1 A1的,概率各 50 % 50\% 50%。继承 A 0 A_0 A0的话与 B B B相同的概率是 P ( A 0 , B ) P(A_0, B) P(A0,B),继承 A 1 A_1 A1的话与 B B B相同的概率是 P ( A 1 , B ) P(A_1, B) P(A1,B)。那么这个基因与 B B B相同的概率就是:
( P ( A 0 , B ) + P ( A 1 , B ) ) / 2 (P(A_0, B)+P(A_1, B))/2 (P(A0,B)+P(A1,B))/2
因此,A的每个基因与B相同的概率都是 ( P ( A 0 , B ) + P ( A 1 , B ) ) / 2 (P(A_0,B)+P(A_1, B))/2 (P(A0,B)+P(A1,B))/2,具有相同的概率。进而, A A A B B B相同基因的数量概率平均也为 ( P ( A 0 , B ) + P ( A 1 , B ) ) / 2 (P(A_0, B)+P(A_1, B))/2 (P(A0,B)+P(A1,B))/2 A A A B B B的相似程度 P ( A , B ) = ( P ( A 0 , B ) + P ( A 1 , B ) ) / 2 P(A, B)=(P(A_0,B)+P(A_1, B))/2 P(A,B)=(P(A0,B)+P(A1,B))/2
这样就归纳证明了 P ( A , B ) P(A, B) P(A,B)的概率递推公式。
下面总结一下前面得出的结论:
(1)边界条件:
A = B : P ( A , B ) = 1 A=B:P(A, B)=1 A=B:P(A,B)1
② A与B无相同基因: P ( A , B ) = 0 ; P(A, B)=0; P(A,B)=0
(2)递推关系:
P ( A , B ) = ( P ( A 0 , B ) + P ( A 1 , B ) ) / 2 P(A, B)=(P(A_0, B)+P(A_1, B))/2 P(A,B)(P(A0,B)+P(A1,B))/2,其中 A 0 A_0 A0 A 1 A_1 A1 A A A的父母
有了边界条件和递推关系,以及Family Tree的阶段性和无后效性作为前提,用动态规划解决问题的所有条件都已满足。应该说,从理论上来讲,本题已经完全解决。下面需要讨论的仅仅是实现方法而已。
动态规划的实现方法有两种:一种是逆向的递推,另一种是正向的记忆化搜索(递归)。这两种方法都是可行的,区别仅仅在于,递推需要更多的考虑状态的阶段性,按照阶段计算出所有状态的值;而记忆化搜索只需要承认状态具有阶段性,无需考虑阶段,只需要按照递推式本身设计带记忆化的递归函数即可。
本题给出的仅仅是一棵Family Tree,并没有给出Family Tree的阶段,如果要用递推的话,就必须先给Family Tree分阶段(拓扑排序)。所以,本题显然更适合用记忆化搜索来实现。
另外,需要注意的是,本题所求的答案是有多少精度就输出多少精度。 300 300 300个节点的图,少说也可以构成几十层的Family Tree,算出的结果至少也有小数点后几十位。所以,高精度是必不可少的(保险起见,可以设置 300 300 300位的高精度)。
分析一下本题的时间复杂度。动态规划的状态有 n 2 n^2 n2个,转移代价为 O ( C ) O(C) O(C)(高精度计算的代价)。因此,时间复杂度为为 O ( C n 2 ) O(Cn^2) O(Cn2) n ≤ 300 n≤300 n300
严格的讲,本题的算法不能算动态规划的方法,因为本题不存在最优化,应该仅仅是一个递推。不过,从分析问题的过程来看,它里面包含了很多动态规划的思想,例如,阶段性和无后效性,特别是使用了动态规划的一种实现方法记忆化搜索。
真不明白出题人怎么出的题…或者出题的时候是怎样的心情…
上代码
Code

大家不要就这么去TyvjA (shui) 题,这个代码超空间…
Tyvj就这么抠给 64 M B 64MB 64MB?
如果大家想A题请用下面Pascal的(P党福音*2)
//由学长的未完成代码改编

var
 dp:array[0..300,0..300]of longint;
 p:array[0..300,0..300]of longint;  //p[i,j]:点i的儿子有p[i,j]   p[i,0]:点i有p[i,0]个儿子
 w,x:array[0..300,1..2]of longint;  //x[i,1/2]:点i的父母     w[i,1]:拓扑点i的原数组点为w[i,1]     w[i,2]:原点i的拓扑点为w[i,2]
 rudu:array[0..300]of longint;   //点i的入度
 tt:array[0..90000,-1..300]of integer;
 z:array[0..300]of longint;
 i,j,k,l,o:longint;
 n,m,ans:longint;
 a,b,c,v:longint;

procedure did(kk,a,b:longint);
var i,max,yu,flag:longint;
begin
 if tt[a,-1]>tt[b,-1]
 then max:=tt[a,-1]
 else max:=tt[b,-1];
 yu:=0;tt[kk,-1]:=max;
 for i:=tt[kk,-1] downto 1 do
  begin
   tt[kk,i]:=(tt[a,i]+tt[b,i]+yu) mod 10;
   yu:=(tt[a,i]+tt[b,i]+yu) div 10;
  end;
 tt[kk,0]:=tt[a,0]+tt[b,0]+yu;
for i:=tt[kk,-1] downto 1 do 
	begin
	if tt[kk,i]<>0 then break;
	dec(tt[kk,-1]);
	end;
 yu:=(tt[kk,0] mod 2)*10;
tt[kk,0]:=tt[kk,0] div 2;
 for i:=1 to tt[kk,-1] do
  begin
   flag:=(yu+tt[kk,i]) mod 2;
   tt[kk,i]:=(yu+tt[kk,i]) div 2;
   yu:=flag*10;
  end;
 if yu<>0 then
	 begin
	 inc(tt[kk,-1]);
	 tt[kk,tt[kk,-1]]:=5;
end;
end;

function f(a,b:longint):longint;
var c:longint;
begin
  if (w[a,2]<w[b,2])
  then begin c:=a; a:=b; b:=c; end;

  if tt[dp[a,b],0]<>-1
  then exit(dp[a,b])
  else
   begin
    did(dp[a,b],f(x[a,1],b),f(x[a,2],b));//dp[a,b]:=(f(x[a,1],b)+f(x[a,2],b))/2;
    exit(dp[a,b]);
   end;
end;

begin
 readln(n,m);
 for i:=1 to m do
  begin
   readln(a,b,c);
   x[a,1]:=b; x[a,2]:=c;
   inc(rudu[a],2);
   inc(p[b,0]); inc(p[c,0]);
   p[b,p[b,0]]:=a; p[c,p[c,0]]:=a;
  end;

 while w[0,1]<>n do
  begin
  z[0]:=0;
  for i:=1 to n do
   if rudu[i]=0
   then
    begin
     inc(w[0,1]); w[i,2]:=w[0,1]; w[w[0,1],1]:=i;
     for j:=1 to p[i,0] do
      begin
       inc(z[0]);
       z[z[0]]:=p[i,j];
      end;
     rudu[i]:=-1;
    end;
  for i:=1 to z[0] do
   dec(rudu[z[i]]);
  end;

 o:=0;
 for i:=1 to n do
  for j:=1 to n do
   if i=j
   then begin inc(o); dp[i,j]:=o; tt[o,0]:=100; end
   else
    if (x[i,1]=0)and(x[j,1]=0)
    then begin  inc(o); dp[i,j]:=o; tt[o,0]:=0; end
    else begin  inc(o); dp[i,j]:=o; tt[o,0]:=-1; end;

 readln(m);
 for i:=1 to m do
  begin
   readln(a,b);
   ans:=f(a,b);
   if tt[ans,-1]=0 then begin
	   write(tt[ans,0]); end
	else begin
	   write(tt[ans,0]);write('.');
		for j:=1 to tt[ans,-1] do
			write(tt[ans,j]);
		end;
	writeln('%');
  end;
end.

学校题库上两个都可以A

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值