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

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 #xy" 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
2017 Multi-University Training Contest - Team 1 
 

Recommend
liuyiding
 

题目大意:

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


解题思路:

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

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

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

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

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

HDU 6041 I Curse Myself(仙人掌+tanjan)_第1张图片


AC代码:

#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
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 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 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]=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]


你可能感兴趣的:(算法,图论)