HDU 6041 - I Curse Myself(仙人掌图DFS找环 + 有限队列求前k大)
I Curse Myself
Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 2911 Accepted Submission(s): 672
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.
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).
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
2017 Multi-University Training Contest - Team 1
首先找到仙人掌图上的环,那么所求问题的补集就是从每个环中删除一个元素,求出删除元素总和中的第 K 大
类似于有向图tarjan的做法,选取一个点作为树根,跑DFS,遇到一个新的结点给她一个值,表示这是第几个访问到的,用数组把这个值存下来。如果下一个结点,她的dfn值比当前结点的小说明这个点在之前已经访问过了,也就是说构成了环。然后把环上面的值都存起来。得到一连串的数组。接下来就是从每个数组中取一个值,加起来,求这一堆值里面的前k大的值。
把每个数组从大到小排序,每次把两个数组合并起来。
比如a,b两个数组,b数组的每一项加上a数组的最大项,把值用有限队列保存起来。每次从有限队列里取最大的值,加到c数组中。取过的b[i]跟a的下一项相加,再保存到优先队列里。
比如b[0] + a[0],b[1] + a[0],b[2] + a[0]保存到了优先队列q里面
然后取出q里面的最大值,因为a,b都是从大到小排序,所以b[0] + a[0]最大,把它取出来然后b[0] + a[1] 存到q中。这是q里面的最大值只可能是b[1] + a[0] 或者b[0] + a[1]。然后取出最大值,b[i]跟a的下一项相加。一直取到k项或者取完所有的组合
#include<bits/stdc++.h>
using namespace std;
#define mem(a) memset(a,0,sizeof(a))
#define ll long long
const ll mod=(ll)pow(2,32);
int w[1010][1010],dfn[1010],len,num,fa[1010],k;
vector<int> a[1010],g[1010],f,tf;
bool cmp(int i,int j) {return i > j;}
void tarjan(int u)
{
dfn[u] = ++num;//给当前结点一个值表示第几个访问到
for (int i = 0;i<g[u].size();i++)
{
int v = g[u][i];
if (v == fa[u]) continue;//fa数组用来存结点的父节点是哪个
if (!dfn[v])//如果下一个结点没有访问过
{
fa[v] = u;
tarjan(v);
}
else if (dfn[v] < dfn[u])//如果下一个结点的dfn比当前的小,则构成了环
{
int j = u;
a[++len].push_back(w[u][v]);
while (j != v)//顺着fa数组一直到v把环上的权值都存到数组里
{
a[len].push_back(w[j][fa[j]]);
j = fa[j];
}
sort(a[len].begin(),a[len].end(),cmp);//数组从大到小排序,方便后面的操作
}
}
}
struct node{
int v,now; //value is (v), now add with (now)
node(int a = 0,int b = 0):v(a),now(b){}
friend bool operator<(const node &x,const node &y)
{
return x.v < y.v;
}
};//small to big
int main()
{
int n,m,count = 0;
while (scanf("%d%d",&n,&m) != EOF)
{
int tot = 0;
for (int i = 1,u,v,z;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&z);
tot += z;
w[u][v] = w[v][u] = z;
g[u].push_back(v);
g[v].push_back(u);
}
scanf("%d",&k);
tarjan(1);
f.clear();
if (len == 0) f.push_back(0);//如果没有环
else
{
node q;
for (int j = 0;j<a[1].size() && j < k;j++) f.push_back(a[1][j]);
for (int i = 2;i<=len;i++)
{
int cnt = f.size() * a[i].size();//判断能产生多少种组合
if (cnt > k) cnt = k;
priority_queue<node> q;
for (int j = 0;j<a[i].size();j++) q.push(node(f[0] + a[i][j],0));//用f里最大的数加到a[i]里,数值用优先队列保存,0表示a[i][j]这数目前与f[0]相加
tf.clear();
while (cnt--)
{
node p = q.top();
q.pop();
tf.push_back(p.v);//每次取最大的值保存到tf数组中
if(p.now != f.size()-1)
{
p.v -= f[p.now++];//当前a[i][j] 跟 f 的下一项结合
p.v += f[p.now];
q.push(p);
}
}
swap(f,tf);
}
}
ll ans = 0;
for (int i = 0;i<f.size();i++) ans = ( ans + 1ll*(i+1)*(tot - f[i]) % mod ) % mod;
printf("Case #%d: %lld\n",++count,ans);
for (int i = 1;i<=n;i++) g[i].clear(),a[i].clear();;
mem(dfn); mem(fa);
num = len = 0;
}
}
本文来自博客园,作者:Un-Defined,转载请保留本文署名Un-Defined,并在文章顶部注明原文链接:https://www.cnblogs.com/EIPsilly/p/17463700.html