2017多校训练赛第一场 HDU 6041 I Curse Myself(仙人掌图生成树)

来源:互联网 发布:avfun邀请码淘宝 编辑:程序博客网 时间:2024/06/09 07:00

I Curse Myself

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

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, defineV(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 31 2 11 3 21 4 313 31 2 12 3 23 1 346 71 2 41 3 23 5 71 5 32 4 12 6 26 4 57

Sample Output

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

Source

2017 Multi-University Training Contest - Team 1



        现在才来补这道题,是不是有点太慢了……

        题意比较简单,给你一幅图,让你求这幅图的生成树的带权值和,即最小生成树乘1,次小生成树乘2,次次小生成树乘3……然后k可以很大,图的点只有1000个。还有特别重要的条件,每一条边都最多在一个简单环中,所以说图是一个仙人掌图。

        如果说没有仙人掌图的条件,那么这题很难做。我们首先建立出最小生成树,可以把边分为树边(在最小生成树中的边)和非树边(不在树上的边)。然后根据仙人掌图的性质,任何非树边对应的环上的树边替换成该非树边,就会构成一棵新的生成树。所以说,对于某一条非树边,我们可以知道用该非树边替代其对应环上的树边后的生成树大小。由于我们要依次求出从小到大共k个生成树,所以要在所有替代方案中选择最优的k个。另外,注意到,我们的生成树完全可以进行组合的替换,即我可以第i条非树边替换的同时第j条非树边也进行了替换(当然也可以不替换)。

       现在, 对于每一个非树边,我们都可以建立一个集合,集合中的第i个元素表示用该非树边替代环上第i个树边的权值增量。如此一来,就有m-n+1个集合,现在问题就变成了,在这m-n+1个集合中,每次可以取出一些元素组合,总共可以取k次,然后问最后k次取都取最小的方案结果。这是一个经典问题的拓展。我们先考虑只有两个集合的情况,相当于从两个集合中取k个数字,使得最后和最小。设集合为a和b,那么经典做法就是建立一个小根堆,对两个集合进行小到大的排序之后,在堆中加入a1+b1,a2+b1,a3+b1,……,an+b1,然后每次取出一个最小的,假设第一次取出的最小的是ai+b1,那么就再加入ai+b2到堆中,以此类推取k个。另外,两个集合中要加入一个最小元素0,表示从该集合中不取数字的情况。现在,我们再考虑m-n+1个集合的情况,很显然方法类似,我们两个两个的合并每次取出k个元素就停止,然后者取出的k个元素作为当前结果集合再与第三个集合进行合并,接着第四个第五个……直到最后。

        接着,我们来分析一下比较玄学的复杂度。首先总共有m-n+1个集合,然后每次取出k个元素,再加上堆的log,正常下来的复杂度为O(NKlogK),对于K最大为10^5的数据显然会超时。但是,考虑到堆的logK,其实可以不那么大,如果每次我保证堆的大小为合并过程中小的那个集合的大小,即一开始加入堆的元素个数为小的那个集合的元素个数,那么最后复杂度总和可以降到理想压线范围,当然了这个还得依赖于比较好的空间复杂度。然后不知道题解怎么就推出来,说复杂度直接降到了O(NK),表示我不理解……

        然后本题涉及到的东西还是有很多的,首先要用并查集+kruskal求一遍最小生成树,确定树边和非树边;然后,求非树边对应的树边用暴力LCA的方法进行枚举;接着,堆的操作用优先队列实现;最后,集合的存储我们是边求集合边合并,用上滚动数组节省空间防止超时。至于那个模2^32-1,其实用了unsigned之后就可以不管它了……具体见代码:

#include<bits/stdc++.h>#define M 100100#define N 1010using namespace std;int ls[N],fa[N],nxt[N<<2],f[N],n,m,e,ee,K;struct Edge{int x,y,w;} gra[N<<1],g[N<<2];int tot[2],h[2][M],fs[N],depth[N];struct node{int w,u,v;};bool v[N<<1];unsigned sum;inline void addedge(int x,int y,int w)//存储最小生成树{    g[++e]=(Edge){x,y,w};    nxt[e]=ls[x]; ls[x]=e;}bool cmp(Edge a,Edge b){    return a.w<b.w;}bool operator < (node a,node b){    return a.w>b.w;}int find(int x)//并查集{    return f[x]==x? x:(f[x]=find(f[x]));}inline unsigned kruskal()//kruskal求最小生成树并标记边的类型{    int t=0; unsigned res=0;    sort(gra+1,gra+1+ee,cmp);    for(int i=1;i<=ee;i++)    {        if (find(gra[i].x)==find(gra[i].y)) continue;        f[find(gra[i].x)]=find(gra[i].y);        addedge(gra[i].x,gra[i].y,gra[i].w);        addedge(gra[i].y,gra[i].x,gra[i].w);        res+=gra[i].w; v[i]=1; if (++t==n-1) break;    }    return res;}inline void getset(int x,int y,int w,int cur)//求非树边对应的树边的替换后边权改变的值{    if (depth[x]>depth[y]) swap(x,y);    for(;depth[x]<depth[y];y=fa[y])//暴力求LCA        h[cur][++tot[cur]]=w-fs[y];//存储边权改变值    for(;x!=y;x=fa[x],y=fa[y])    {        h[cur][++tot[cur]]=w-fs[y];//fs[i]表示i到它的父亲的边的边权        h[cur][++tot[cur]]=w-fs[x];    }    sort(h[cur]+1,h[cur]+1+tot[cur]);//对集合元素进行排序}inline void dfs(int x,int father,int dep){    depth[x]=dep;//dfs求节点深度以及父亲    fa[x]=father;//为LCA做准备    for(int i=ls[x];i;i=nxt[i])    {        int y=g[i].y;        if (y==father) continue;        fs[y]=g[i].w;//计算节点到父亲的边权        dfs(y,x,dep+1);    }}int main(){    int T_T=0;    while(~scanf("%d%d",&n,&m))    {        e=ee=0;        for(int i=1;i<=n;i++) f[i]=i,ls[i]=0;        for(int i=1;i<=m;i++)        {            int x,y,w; v[i]=0;            scanf("%d%d%d",&x,&y,&w);            gra[++ee]=Edge{x,y,w};        }        scanf("%d",&K);        sum=kruskal();        dfs(1,0,0);        int cur=0,pre=1;        tot[cur]=tot[pre]=0;        for(int i=1;i<=ee;i++)        {            if (v[i]) continue;            tot[cur]=1; h[cur][1]=0;//tot表示集合大小            getset(gra[i].x,gra[i].y,gra[i].w,cur);            if (!tot[pre]) {cur^=1,pre^=1;continue;}//如果是暂时只有一个集合,那么不用合并            if (tot[cur]>tot[pre]) cur^=1,pre^=1;//初始时加入的元素个数为小的那个集合的元素个数            priority_queue<node> q;            for(int j=1;j<=tot[cur];j++)//加入尽量少的元素减少复杂度                q.push(node{h[cur][j]+h[pre][1],j,1});            tot[cur]=0;            while(!q.empty())            {                node now=q.top(); q.pop();                h[cur][++tot[cur]]=now.w;//取出堆中元素放入另一个已经不需要的集合中                if (now.v<tot[pre])                    q.push(node{now.w-h[pre][now.v]+h[pre][++now.v],now.u,now.v});//新的点压入堆                if (tot[cur]==K) break;//取了k个元素就退出            }            cur^=1,pre^=1;//数组滚动        }        unsigned ans=sum;        for (int i=2;i<=min(K,tot[pre]);i++)            ans+=(unsigned)i*(h[pre][i]+sum);//计算结果时,由于用的是差值,所以加上一个最小生成树的边权值        printf("Case #%d: %u\n",++T_T,ans);    }    return 0;}

原创粉丝点击