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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值