HDU 6041 I Curse Myself(仙人掌+tanjan)

87 篇文章 0 订阅

I Curse Myself

Time Limit: 8000/4000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 475    Accepted Submission(s): 76


Problem Description
There is a connected undirected graph with weights on its edges. It is guaranteed that each edge appears in at most one simple cycle.

Assuming that the weight of a weighted spanning tree is the sum of weights on its edges, define  V(k)  as the weight of the  k -th smallest weighted spanning tree of this graph, however,  V(k)  would be defined as zero if there did not exist  k  different weighted spanning trees.

Please calculate  (k=1KkV(k))mod232 .
 

Input
The input contains multiple test cases.

For each test case, the first line contains two positive integers  n,m   (2n1000,n1m2n3) , the number of nodes and the number of edges of this graph.

Each of the next  m  lines contains three positive integers  x,y,z   (1x,yn,1z106) , meaning an edge weighted  z  between node  x  and node  y . There does not exist multi-edge or self-loop in this graph.

The last line contains a positive integer  K   (1K105) .
 

Output
For each test case, output " Case # x y " in one line (without quotes), where  x  indicates the case number starting from  1  and  y  denotes the answer of corresponding case.
 

Sample Input
  
  
4 3 1 2 1 1 3 2 1 4 3 1 3 3 1 2 1 2 3 2 3 1 3 4 6 7 1 2 4 1 3 2 3 5 7 1 5 3 2 4 1 2 6 2 6 4 5 7
 

Sample Output
  
  
Case #1: 6 Case #2: 26 Case #3: 493
 

Source
 

Recommend
liuyiding
 

题目大意:

    给你一棵无向带边权的仙人掌(节点数<=1e3),求前k(1<=k<=1e5)小生成树的权值之和。


解题思路:

    由于这个图是仙人掌,那么它的生成树就一定是每个环去掉一条边所构成的,我们可以通过存边的tarjan算法找到仙人掌上的所有环,题目要求k小生成树,我们只要找到去掉的边的k大的组合即可。

    那么就可把问题简化为:有一些集合,在每个集合中选一个数,求前k大的组合。

    官方题解说这就是一个经典问题了。。。可能是我比较菜吧,通过这道题,我才知道这个问题怎么解决。。。。

    对于这些集合两两合并(为了方便一个叫A一个叫B),在合并的过程中,先用A中最大值和B中全部元素相加,放到堆中。每次从堆中拿出元素时,用构成这个元素的A中下一个更小的值(如果存在)与之前的B中元素结合放到堆中,直到堆空或得到k个元素。

    这样写的复杂度我不是很会推导,引用官方题解:



AC代码:

#include <iostream>  
#include <algorithm>  
#include <cstdio>  
#include <cstring>  
#include <cstdlib>  
#include <cmath>  
#include <ctime>  
#include <vector>  
#include <queue>  
#include <stack>  
#include <deque>  
#include <string>  
#include <map>  
#include <set>  
#include <list>  
using namespace std;  
#define INF 0x3f3f3f3f  
#define LL long long  
#define fi first  
#define se second  
#define mem(a,b) memset((a),(b),sizeof(a))  

const int MAXV=1000+3;  
const int MAXK=100000+3;  
const int MAXE=MAXV*4;  

struct Edge  
{  
    int to, cost, next;  
    Edge(int t=0, int c=0, int n=0):to(t), cost(c), next(n){}  
}edge[MAXE];  

struct Val//合并后的边  
{  
    int val,id1,id2;  
    Val(int val,int id1,int id2):val(val),id1(id1),id2(id2){}  
    bool operator < (const Val &other)const  
    {  
        return val<other.val;  
    }  
};  

int head[MAXV];//邻接表头节点  
int V,E,K;//节点数,边数,查询的K大  
int dfn[MAXV],low[MAXV],tmpdfn;  
bool vis[MAXV];  
stack<int> st;//保存边  
int res[MAXK],tmp_vector[MAXK],save[MAXK];//这题卡常数,这里用数组代替vector,下标0表示数组长度,其它位置存值

void init()//初始化
{  
    for(int i=1;i<=V;++i)  
    {  
        head[i]=-1;  
        vis[i]=0;  
    }  
    res[0]=0;  
    tmpdfn=0;  
}  

void unite(int *A, int *B)//合并两个集合
{  
    priority_queue<Val> que;
    for(int i=1;i<=B[0];++i)//先放把A中最小的元素和B中元素相加  
        que.push(Val(A[1]+B[i],1,i));  
    tmp_vector[0]=0;  
    while(tmp_vector[0]<K&&!que.empty())  
    {  
        Val it=que.top(); que.pop();  
        tmp_vector[++tmp_vector[0]]=it.val;  
        if(it.id1+1<=A[0])//B中元素不变,此时A中比使用的的大的元素和B加起来,放到堆中  
        {  
            ++it.id1;  
            que.push(Val(B[it.id2]+A[it.id1], it.id1, it.id2));  
        }  
    }  
    for(int i=0;i<=tmp_vector[0];++i)  
        A[i]=tmp_vector[i];  
}  

void tarjan(int u, int fa)//tanjan找到仙人掌上所有的环  
{  
    dfn[u]=low[u]=tmpdfn++;  
    vis[u]=true;  
    for(int i=head[u];~i;i=edge[i].next)  
    {  
        int v=edge[i].to;  
        if(v==fa)  
            continue;  
        if(!vis[v])  
        {  
            st.push(i);  
            tarjan(v, u);  
            low[u]=min(low[u], low[v]);  
            if(low[v]>=dfn[u])//u是割点  
            {  
                save[0]=0;  
                int tmp;  
                do{  
                    tmp=st.top(); st.pop();  
                    save[++save[0]]=edge[tmp].cost;  
                }while(tmp!=i);  
                if(save[0]>1)//找到环  
                    unite(res,save);  
            }  
        }  
        if(vis[v]&&dfn[v]<dfn[u])//防止一个边被加入多次  
        {  
            st.push(i);  
            low[u]=min(low[u], dfn[v]);  
        }  
    }  
}  

int main()  
{  
    int cas=1;  
    while(~scanf("%d%d",&V,&E))  
    {  
        init();  
        int sum=0;  
        for(int i=0;i<E;++i)//建图  
        {  
            int u,v,c;  
            scanf("%d%d%d",&u,&v,&c);  
            edge[i<<1]=Edge(v, c, head[u]);  
            head[u]=(i<<1);  
            edge[(i<<1)|1]=Edge(u, c, head[v]);  
            head[v]=((i<<1)|1);  
            sum+=c;  
        }  
        scanf("%d",&K);  
        res[++res[0]]=0;  
        tarjan(1,-1);  
        unsigned ans=0;  
        for(int i=1;i<=res[0];++i)//把前K小的生成树权值加起来  
            ans+=i*(sum-res[i]);//因为是对2^32取模,所以使用unsignd int的时候,会溢出自动取模  
        printf("Case #%d: %u\n",cas++,ans);  
    }  
    
    return 0;  
}  


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值