您的位置:首页 > 其它

codeforces gym 2016-2017 NEERC, Moscow Subregional K. Knights of the Old Republic 最小生成树+dp

2016-11-14 16:27 489 查看
注:本文思路部分引自鸟神的题解
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 3e6+5;
typedef __int64 ll;
ll a
, b
, s
, fa
;

struct edge
{
int u, v, val;
}ed
;

bool cmp( edge a, edge b )
{
return a.val < b.val;
}

int root( int x )
{
if( fa[x] == x ) return x;
else{
fa[x] = root( fa[x] );
return fa[x];
}
}

int main()
{
int n, m, i;
cin >> n >> m;
for( i = 1; i <= n; i ++ ) fa[i] = i;
for( i = 1; i <= n; i ++ ) scanf("%I64d%I64d", &a[i], &b[i]), s[i] = a[i]*b[i];
for( i = 1; i <= m; i ++ ){
scanf("%d %d %d", &ed[i].u, &ed[i].v, &ed[i].val);
}
sort( ed+1, ed+m+1, cmp );
for( i = 1; i <= m; i ++ ){
int x = root(ed[i].u), y = root(ed[i].v);
if( x != y ){
ll A = max( a[x], max( a[y], (ll)ed[i].val ) ), B = min( b[x], b[y] );
//A取三者最大值,B取两个点集内最小单个费用
s[x] = min( s[x]+s[y], A*B );
a[x] = A;//注意修改,维护的是点集内能有多少个自由移动的点
b[x] = B;
fa[y] = x;
}
}
ll ans = 0;
for( i = 1; i <= n; i ++ ){
if( fa[i] == i ) ans += s[i];//存在不同的连通块
}
cout << ans << endl;
}
/*
题意:给n个点,每个点需要传送a个大哥,穿一个大哥需要b的费用,每条边导通的条件是两边的大哥数量大于边权
只要每个点有过,注意,有过a个大哥即可,问最小花费
思路:
蜜汁最小生成树
这样我们可以维护一个dp,st代表点集,dp[st]代表搞定了st的所有需求花费的最小费用
对于两个点集p和q
如果不连这条边 答案为dp[p]+dp[q]
如果连,那么这两个点集内边权比这条边权小的边就必然会被导通,因为大的既然都能自由移动了,小的不还是很容易嘛
(这里不必考虑点集内具体的连边情况)

那这样的话,不妨从小到大枚举边,因为这样则两个点集内的点必然会导通
用krukal实现这样的过程,搞出了一个最小生成树

这么写可能有很多地方觉得不一定对,但仔细观察合并过程,大部分情况都是考虑进来的
有点像二极管的导通啊~
*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  最小生成树 dp
相关文章推荐