2020 ICPC Asia Taipei-Hsinchu Regional H - Optimization for UltraNet (生成树 + 二分)

在这里插入图片描述
题意:题目略长,就是让你求一棵生成树,这棵树要满足的条件时最小的边尽可能的大,同时其它的边又要尽可能的小。

思路:看到求最小边最大,就可以联想到,把边权排序之后,去二分枚举哪一条才是符合要求的。每次找到一条边,因为我们是按边权值排好序的,那么我们只需要从这个最小边去顺着找,看能否组成一棵树,最后二分出来答案。
根据题目的描述我们可以知道,计算这棵树的大小时,是每次我们找到这棵树的最小边然后计算这条边的贡献,(即经过这条的边的所有路径的路径和),下一次计算就把这条边去掉。
换成公式来讲就是当前边的权值为w,左侧连接的子树大小为a,右侧子树的大小为b,它的贡献就是w * a * b。
这样我们二分答案后,就可以统计答案了,统计答案案的方法是,选出当前树中的最小边,然后算出他的贡献,再去递归的去它左右子树中去做重复的操作,注意算完当前贡献后,要把这条边标记掉,也就相当于把这条边从图中删去。
另外,因为加边加的双向边,所以我们可以用id 和 id^1这个小技巧更新。
一个数^1,如果这个数是奇数那么他就-1,偶数就减+1,也就是说可以用作成对变化的小技巧

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int MAXN = 1e6 + 7;
const int M = 5e5 + 7;
const int INF = 0x3f3f3f3f;
int n,m;
int head[MAXN],cnt;
struct Edge{
	int to,next,w;
}edge[MAXN];
struct node{
	int x,y,w;
}in[MAXN];
void addedge(int u,int v,int w){
	edge[++cnt].to = v;
	edge[cnt].w = w;
	edge[cnt].next = head[u];
	head[u] = cnt;
}
bool cmp(node a,node b){ return a.w < b.w; }
/*题目中给出 一条边对答案的贡献应该是 当前的边权值 x 左侧子树大小 x 右侧子树大小 */

/*枚举边的时候 判断能够形成生成树否*/
#define MP make_pair
#define pb push_back
// vector<pair<int,int>>vec;
int pre[MAXN];
int Find(int x){
	if(pre[x] ==  x) return x;
	else return pre[x] = Find(pre[x]);
}
bool merge(int x,int y){
	int fx = Find(x),fy = Find(y);
	if(fx != fy){
		pre[fx] = fy;return true;
	}
	return false;
}
bool check(int pp,int flag){//标志为1的时候代表 统计答案
	int num = 0;
	for(int i = 1;i <= n;i ++) pre[i] = i;
	for(int i = pp;i <= m;i ++){
		int fx = Find(in[i].x),fy = Find(in[i].y);
		if(merge(fx,fy)){
			if(flag){
				addedge(in[i].x,in[i].y,in[i].w);
				addedge(in[i].y,in[i].x,in[i].w);
				// vec.pb(MP(in[i].x,in[i].y));
			}
			num++;
		}
		if(num >= n - 1) return true;
	}
	return false;
}
/**/
//小技巧 因为加边的时候是双向加边的 所以一条边其实对应两个连续的编号 可以用i^1和i来完成标记
/*统计答案*/
int vis[MAXN],siz[MAXN];
int from,to,minn,id;
ll sum;
void cal(int u,int fa){
	siz[u] = 1;
	for(int i = head[u];i;i = edge[i].next){
		int v = edge[i].to;
		if(vis[i] || v == fa) continue;
		cal(v,u);
		siz[u] += siz[v];
		if(edge[i].w < minn){//记录下来选取出来的最小边 统计贡献
			minn = edge[i].w;
			id = i;
			from = u,to = v;
		}
	}
}
ll dfs(int u){
	int f = 0;
	for(int i = head[u];i;i = edge[i].next){
		if(vis[i]) continue;
		f = 1;break;
	}
	if(!f) return 0;//都遍历过了已经

	minn = INF,id = 0;
	cal(u,-1);
	// cout<<"************\n"<<from<<' '<<to<<'\n'<<siz[from]<<' '<<siz[to]<<"\n************\n";
	int tid = (id & 1) ? id + 1 : id - 1;
	vis[id ^ 1] = 1,vis[id] = 1;
	// cout<<id<<' '<<(id ^ 1)<<endl;
	ll t = 1ll * (siz[u] - siz[to]) * siz[to] * minn;
	// cout<<u<<' '<<to<<':'<<siz[u]<<' '<<siz[to]<<'\n';
	int t1 = from,t2 = to;//这个地方 要再用两个变量记录一下 要不然直接from和to会造成混乱 会WA
	return t + dfs(t1) + dfs(t2);//递归求解
}
/*********************/
int main(){
	cnt = 1;
	scanf("%d%d",&n,&m);
	int u,v,w;
	for(int i = 1;i <= m;i ++){
		scanf("%d%d%d",&in[i].x,&in[i].y,&in[i].w);
	}
	sort(in + 1,in + 1 + m,cmp);
	int l = 1,r = m,ans = 1;
	while(l <= r){//二分最小边 因为枚举的是最小边 这时候再去看 剩下的边能否生成 一棵树即可
		int mid = l + r >> 1;
		if(check(mid,0)) ans = mid , l = mid + 1;
		else r = mid - 1;
	}
	// cout<<ans<<endl;
	check(ans,1);//准备统计答案
	// for(int i = 0;i < vec.size();i ++) cout<<vec[i].first<<' '<<vec[i].second<<'\n';
	printf("%lld\n",dfs(1));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值