[bzoj3754]Tree之最小方差树【暴力】【MST】

【题目描述】

Description

Wayne在玩儿一个很有趣的游戏。在游戏中,Wayne建造了N个城市,现在他想在这些城市间修一些公路,当然并不是任意两个城市间都能修,为了道路系统的美观,一共只有M对城市间能修公路,即有若干三元组 (Ui,Vi,Ci)表示Ui和Vi间有一条长度为Ci的双向道路。当然,游戏保证了,若所有道路都修建,那么任意两城市可以互相到达。Wayne拥有恰好N-1支修建队,每支队伍能且仅能修一条道路。当然,修建长度越大,修建的劳累度也越高,游戏设定是修建长度为C的公路就会有C的劳累度。当所有的队伍完工后,整个城市群必须连通,而这些修建队伍们会看看其他队伍的劳累情况,若劳累情况差异过大,可能就会引发骚动,不利于社会和谐发展。Wayne对这个问题非常头疼,于是他想知道,这N1支队伍劳累度的标准差最小能有多少。
标准差的定为:设有N个数,分别为ai,它们的平均数为  ,那么标准差就是

Input

第一行两个正整数N,M
接下来M行,每行三个正整数Ui,Vi,Ci

Output

输出最小的标准差,保留四位小数。

Sample Input

3 3
1 2 1
2 3 2
3 1 3

Sample Output

0.5000

HINT

N<=100,M<=2000,Ci<=100

Source

【题解】

 把标准差看成方差Wa了无数发。。

对于任意一种选取方案,若把 a的平均值 看做自变量x,暴力展开可知:当 x==a的平均值 时,标准差取到最小值。

因此可以枚举 a的平均值 。

若每隔 1/(n-1) 枚举一次, 复杂度太高无法通过。

但由于 a 为整数,所以对于两条边权值分别为 ax, ay(ax<ay), 当枚举 [ax..mid) 或 (mid..r] 时,选取方案始终相同。

因此只要枚举在两边的情况和中间的情况。

具体来说,只要每隔0.25枚举一次。

/* --------------
    user Vanisher
    problem bzoj-3754 
----------------*/
# include <bits/stdc++.h>
# define 	ll 		long long
# define 	M 		2010
# define 	N 		110
using namespace std;
int read(){
	int tmp=0, fh=1; char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') fh=-1; ch=getchar();}
	while (ch>='0'&&ch<='9'){tmp=tmp*10+ch-'0'; ch=getchar();}
	return tmp*fh;
}
struct node{
	int u,v,w;
	double vote;
}e[M];
int f[N],n,m;
int dad(int x){
	if (f[x]==x) return x;
		else return f[x]=dad(f[x]);
}
double sqr(double x){
	return x*x;
}
bool cmp(node x, node y){
	return x.vote<y.vote;
}
double check(double x){
	for (int i=1; i<=m; i++)
		e[i].vote=sqr(e[i].w-x);
	sort(e+1,e+m+1,cmp);
	for (int i=1; i<=n; i++) f[i]=i;
	int num=n,i=0;
	double sum=0,now;
	while (num>1){
		i++; int u=dad(e[i].u), v=dad(e[i].v);
		if (u!=v){
			num--;
			sum=sum+e[i].w;
			f[u]=v;
		}
	}
	now=sum/(n-1); sum=0; num=n;
	for (int i=1; i<=n; i++) f[i]=i;
	i=0;
	while (num>1){
		i++; int u=dad(e[i].u), v=dad(e[i].v);
		if (u!=v){
			num--;
			sum=sum+sqr(e[i].w-now);
			f[u]=v;
		}
	}
	return sqrt(sum/(n-1));
}
int main(){
	n=read(), m=read(); 
	double now=0,ans,ansnow;
	for (int i=1; i<=m; i++){
		e[i].u=read(), e[i].v=read(), e[i].w=read();
		now=now+e[i].w;
	} 
	now=now/m,ans=ansnow=check(now);
/*	for (double T=30; T>0.1; T=T*0.9998){
		int fh=rand()%2; if (fh==0) fh--;
		double tmp=rand()*T/32767*fh+now,anstmp=check(tmp);
		ans=min(ans,anstmp);
		if (anstmp<ansnow)
			now=tmp, ansnow=anstmp;
			else if (exp((ansnow-anstmp)/T)>rand()*1.0/32767)
				now=tmp, ansnow=anstmp;
	}*/
	double eps=1.0/(m-1);
	for (double T=0; T<=100; T+=0.25)
		ans=min(ans,check(T));
	printf("%.4lf\n",ans);
	return 0;
}
 一开始写的退火,知识水平不够根本过不去。。。

转载于:https://www.cnblogs.com/Vanisher/p/9136027.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值