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=1Kk⋅V(k))mod232 .
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=1Kk⋅V(k))mod232 .
Input
The input contains multiple test cases.
For each test case, the first line contains two positive integers n,m (2≤n≤1000,n−1≤m≤2n−3) , 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 (1≤x,y≤n,1≤z≤106) , 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 (1≤K≤105) .
For each test case, the first line contains two positive integers n,m (2≤n≤1000,n−1≤m≤2n−3) , 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 (1≤x,y≤n,1≤z≤106) , 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 (1≤K≤105) .
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;
}