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

版权声明:转载请注明出处:http://blog.csdn.net/yasola,谢谢 https://blog.csdn.net/Yasola/article/details/76158554

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
 

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;  
}  


阅读更多
换一批

没有更多推荐了,返回首页