[题解]血缘关系

本文介绍了一种算法,用于计算妖怪家族成员间的基因相似度。通过构建家谱图并运用记忆化搜索与拓扑排序的方法,实现了对任意两个妖怪间基因相似度的精确计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

我们正在研究妖怪家族的血缘关系。每个妖怪都有相同数量的基因,但是不同的妖怪的基因可能是不同的。我们希望知道任意给定的两个妖怪之间究竟有多少相同的基因。由于基因数量相当庞大,直接检测是行不通的。但是,我们知道妖怪家族的家谱,所以我们可以根据家谱来估算两个妖怪之间相同基因的数量。

妖怪之间的基因继承关系相当简单:如果妖怪C是妖怪A和B的孩子,则C的任意一个基因只能是继承A或B的基因,继承A或B的概率各占50%。所有基因可认为是相互独立的,每个基因的继承关系不受别的基因影响。

现在,我们来定义两个妖怪X和Y的基因相似程度。例如,有一个家族,这个家族中有两个毫无关系(没有相同基因)的妖怪A和B,及它们的孩子C和D。那么C和D相似程度是多少呢?因为C和D的基因都来自A和B,从概率来说,各占50%。所以,依概率计算C和D平均有50%的相同基因,C和D的基因相似程度为50%。需要注意的是,如果A和B之间存在相同基因的话,C和D的基因相似程度就不再是50%了。

你的任务是写一个程序,对于给定的家谱以及成对出现的妖怪,计算它们之间的基因相似程度。

输入输出格式

输入格式:

第一行两个整数n和k。n(2≤n≤300)表示家族中成员数,它们分别用1, 2, …, n来表示。k(0≤k≤n-2)表示这个家族中有父母的妖怪数量(其他的妖怪没有父母,它们之间可以认为毫无关系,即没有任何相同基因)。

接下来的k行,每行三个整数a, b, c,表示妖怪a是妖怪b的孩子。

然后是一行一个整数m(1≤m≤n2),表示需要计算基因相似程度的妖怪对数。

接下来的m行,每行两个整数,表示需要计算基因相似程度的两个妖怪。

你可以认为这里给出的家谱总是合法的。具体来说就是,没有任何的妖怪会成为自己的祖先,并且你也不必担心会存在性别错乱问题。

输出格式:

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

输入输出样例

输入样例#1:
7 4                                                    
4 1 2                                          
5 2 3                                          
6 4 5                                          
7 5 6
4
1 2
2 6
7 5
3 3
输出样例#1:
0%
50%
81.25%
100%


题解:

本题较为麻烦。

首先用f[i][j]表示i与j之间的相似度,fa[i]表示i的父亲,ma[i]表示i的母亲。

有递推式:(这个我想了好久)f[i][j]=(f[fa[i]][j]+f[ma[i]][j])/2,或者是f[i][j]=(f[i][fa[j]]+f[i][ma[j]])/2

至于具体取哪一个下面会讲。

首先初始化各个祖先之间相似度为0。

发现根本不知道按什么顺序推。所以记忆化搜索。b[i][j]记录i和j的关系是否已经得出。

但是这时候问题来了:

如果我们一直取i的父母与j比较,即用递推式f[i][j]=(f[fa[i]][j]+f[ma[i]][j])/2,

有可能出现一个情况:j是i的子辈,那么此时i的父母会离j越来越“远”,这样记忆化搜索永远结束不了。

所以还要用拓扑排序,记录下各个怪物在拓扑序列中的位置,用来比较辈分,每次要取辈分靠后的怪物的父母分别与另一个进行比较。

至于高精度,还是要自己慢慢打了。

详见代码:

#include<bits/stdc++.h>
using namespace std;
#define file(x) freopen(#x".in","r",stdin),freopen(#x".out",stdout)
struct num //高精度运算符重载
{
    int a[302],len;
    num()
    {
        len=1;
        memset(a,0,sizeof a);
    }
    num operator=(num x)
    {
        for(int i=1;i<=x.len;i++)
            a[i]=x.a[i];
        len=x.len;
        return *this;
    }
    void div2()
    {
        for(int i=1;i<=len;i++)
        {
            if(a[i+1]&1)a[i]+=10;
            a[i]/=2;
        }
        while(a[len]==0&&len>1)len--;
    }
    num operator+(num b)
    {
        num a=*this,c;
        int x=0;
        c.len=max(a.len,b.len)+1;
        for(int i=1;i<=c.len;i++)
        {
            c.a[i]=a.a[i]+b.a[i]+x;
            x=c.a[i]/10;
            c.a[i]%=10;
        }
        while(c.a[c.len]==0&&c.len>1)c.len--;
        return c;
    }
};
typedef num number;
ostream& operator<<(ostream &cout,num x)
{
    int a[301],len=x.len,beg=1;
    for(int i=1;i<=x.len;i++)
        a[i]=x.a[i];
    while(len<298)a[++len]=0;
    while(a[beg]==0&&beg<298)beg++;
    for(int i=len;i>=beg;i--)
    {
        if(i==297)printf(".");
        printf("%d",a[i]);
    }
    return cout;

}

//输出自己想办法

number f[301][301];
int fa[301][2],n,m,k,anc[301],dep[301],son[301][301],num[301],sta[301],top=0,fat[301];
//dep是在拓扑序列中的位置,fat是父母数量,anc记录所有的祖先,son是各个节点的儿子们,num是儿子数量
bool b[301][301];//记录关系是否已经得出
number dfs(int x,int y)//记忆化搜索
{
    if(b[x][y])return f[x][y];
    if(dep[x]<dep[y])swap(x,y);//如果x的备份较高,即在拓扑序列中的dep较小,就要交换
    number p;
    b[x][y]=b[y][x]=1;
    p=dfs(fa[x][0],y)+dfs(fa[x][1],y);//取x的父母与y比较
    p.div2();
    return f[y][x]=f[x][y]=p;
}
int main()
{
    //file(family);
    scanf("%d%d",&n,&k);
    for(int i=1,aa,bb,cc;i<=k;i++)
    {
        scanf("%d%d%d",&aa,&bb,&cc);
        son[bb][++num[bb]]=aa;
        son[cc][++num[cc]]=aa;
        fa[aa][0]=bb;
        fa[aa][1]=cc;
        fat[aa]=2;
    }//输入
    for(int i=1;i<=n;i++)
    {
        f[i][i].len=300;
        f[i][i].a[300]=1;//初始化高精度
        b[i][i]=1;
        if(!fa[i][0]&&!fa[i][1])anc[++anc[0]]=i;
    }
    for(int i=1;i<anc[0];i++)
        for(int j=i+1;j<=anc[0];j++)
            b[anc[i]][anc[j]]=b[anc[j]][anc[i]]=1;//祖先之间关系已经得出
    int deep=0;
    for(int i=1;i<=anc[0];i++)
        sta[++top]=anc[i];//拓扑排序,祖先入栈
    while(top>0)//拓扑排序
    {
        int k=sta[top--];
        dep[k]=++deep;//记录深度
        for(int i=1;i<=num[k];i++)
        {
            fat[son[k][i]]--;
            if(!fat[son[k][i]])sta[++top]=son[k][i];
        }
    }
    scanf("%d",&m);
    for(int i=1,u,v;i<=m;i++)
    {
        scanf("%d%d",&u,&v);
        dfs(u,v);
        cout<<f[u][v]<<'%'<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值