POJ 3156 Interconnect

本文详细介绍了使用DP记忆化搜索结合hash解决POJ3156 Interconnect问题的方法。作者分享了从多次调试到最终AC的心路历程,并提供了完整的代码实现,包括如何利用并查集统计连通分量及其节点数量,以及如何通过状态压缩和hash函数加速计算过程。

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

Interconnect


昨天一天都在这个题上面个wa,晚上回来又重新打了一遍,然后就进400多毫秒过了这个题目。。。很是惊讶。然后仔细一看,原来是之前有地方写错了,这让我情何以堪。。。。发现自己打代码的能力越来越差,不能很快的将自己的思路用代码给表现出来。。。还要一直狂DEBUG(现在愈来愈认为DEBUG是一个非常不好的东西。。。)。。。
计算期望的时候:E(i) = pk* E(i) + sum(p1*E[j]) + 1,其中i中的连分块的个数比j中的多一个。。也就是说j是i通过合并两个连分块得到的。。。首先我们可以求出这个图的连分块的个数以及每个连分块的点的数量,直接用并查集就可以完成统计功能。。然后就采用记忆化搜索的方式进行DP。由于状态数不能确定,我们这里可以直接采用hash的方式边求边保存。。这里hash是关键,将对应的每个连分块的数目存进数组里,然后排序,在用BKDRHash.


/*
    author   : csuchenan
    prog     : POJ3156
    algorithm: DP 记忆化搜索+hash
    csuchenan	3156	Accepted	1216K	469MS	C++	2323B	2012-10-23 23:59:50
    用BKDRhash速度快了许多。。。
*/
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
const int mod = 10007;

struct Node{
    int x[31];//连通分量对应的点的个数
    int xs;//连通分量的个数
    double exp;
    void init(){
        memset(x, 0, sizeof(x));
        xs = 0;
    }
    bool operator==(Node &a){
        for(int i = 0; i != 31; i ++){
            if(a.x[i] != x[i])
                return false;
        }
        return true;
    }
    void sort(){
        std::sort(x, x+31);
    }
    //BKDR hash
    unsigned int hash(){
        unsigned int hash = 0, seed = 131;
        for(int i = 30 ; i >=0 && x[i] ; i--){
            hash = hash * seed + x[i];
        }
        return (hash & 0x7fffffff)%mod;
    }
};
std::vector<Node> hh[mod];
double search(Node &a){
    unsigned int x = a.hash();
    for(int i = 0; i != hh[x].size(); i ++){
        if(a == hh[x][i]){
            return hh[x][i].exp;
        }
    }
    return -1;
}
int p[31], tot[31];
int n, m, tu, tv;

int find(int x){
    return x == p[x] ? x : (p[x] = find(p[x]));
}

double DP(Node ost){
    //当只有一个连通块的个数的时候,说明已经连成一体
    if(ost.xs == 1){
        return 0;
    }
    double x = search(ost);
    if(x != -1.0)
        return x;
    double tmp = 0, ans = 0;
    //统计添加边后不会改变现有连通性
    for(int i = 0; i != 31; i ++){
        tmp += ost.x[i] * (ost.x[i]-1)/2;
    }
    //统计添加边后,连通块的个数会减少
    for(int i = 0; i != 31; i ++){
        for(int  j = i+1; j != 31; j ++){
            if(ost.x[i]==0 || ost.x[j]==0)
                continue;
            Node sst = ost;
            sst.x[i] = sst.x[i] + sst.x[j];
            sst.x[j] = 0;
            sst.xs --;
            sst.sort();
            ans += ost.x[i] * ost.x[j] * DP(sst);
        }
    }
    ans = ans / (n*(n-1)/2) + 1 ;
    ans = ans / (1 - tmp/(n*(n-1)/2) ) ;
    ost.exp = ans;
    hh[ost.hash()].push_back(ost);
    return ans;
}

int main(){
    scanf("%d%d", &n, &m);

    for(int i = 1; i != 31; i ++){
        p[i] = i;
        tot[i] = 0;
    }
    //统计连通块的个数以及每个连通块的节点个数
    for(int i = 0; i < m; i ++){
        scanf("%d%d", &tv, &tu);
        p[find(tv)] = find(tu);
    }
    for(int i = 1; i <= n; i ++){
        tot[find(i)] ++;
    }
    Node st;
    st.init();
    for(int i = 1; i <= n; i ++){
        if(tot[i]){
            st.x[st.xs ++] = tot[i];
        }
    }
    //这里一定要排序,便于后面判断两个状态相等
    st.sort();
    printf("%.10lf\n", DP(st));
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值