题意:题目略长,就是让你求一棵生成树,这棵树要满足的条件时最小的边尽可能的大,同时其它的边又要尽可能的小。
思路:看到求最小边最大,就可以联想到,把边权排序之后,去二分枚举哪一条才是符合要求的。每次找到一条边,因为我们是按边权值排好序的,那么我们只需要从这个最小边去顺着找,看能否组成一棵树,最后二分出来答案。
根据题目的描述我们可以知道,计算这棵树的大小时,是每次我们找到这棵树的最小边然后计算这条边的贡献,(即经过这条的边的所有路径的路径和),下一次计算就把这条边去掉。
换成公式来讲就是当前边的权值为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;
}