题目大意
给定n个点,m条边,每次只能从小的点走向大的点,问走过路径的最小方差。
分析
此处特别鸣谢Dr.Chen讲解思路非常详细,JACKMA提供的LaTeX公式推导。不想手打
看到方差就先想到方差公式。但是我们要对这个原始公式变形:
首先给出方差定义:
σ
2
=
1
n
∑
i
=
1
n
(
x
i
−
x
ˉ
)
2
\sigma^2=\frac{1}{n}\sum_{i=1}^n(x_i-\bar x)^2
σ2=n1i=1∑n(xi−xˉ)2
x
ˉ
\bar x
xˉ 是平均数,
x
ˉ
=
1
n
∑
i
=
1
n
x
i
\bar x=\frac{1}{n}\sum_{i=1}^nx_i
xˉ=n1i=1∑nxi
然后考虑化简等号右边的式子。
原右式等于:
1
n
∑
i
=
1
n
x
i
2
−
2
x
i
x
ˉ
+
x
ˉ
2
\frac{1}{n}\sum_{i=1}^nx_i^2-2x_i\bar x +\bar x^2
n1i=1∑nxi2−2xixˉ+xˉ2
1
n
(
∑
i
=
1
n
x
i
2
−
∑
i
=
1
n
2
x
i
x
ˉ
+
∑
i
=
1
n
x
ˉ
2
)
\frac{1}{n}(\sum_{i=1}^nx_i^2-\sum_{i=1}^n2x_i\bar x +\sum_{i=1}^n\bar x^2)
n1(i=1∑nxi2−i=1∑n2xixˉ+i=1∑nxˉ2)
1
n
(
∑
i
=
1
n
x
i
2
−
2
x
ˉ
∑
i
=
1
n
x
i
+
n
x
ˉ
2
)
\frac{1}{n}(\sum_{i=1}^nx_i^2-2\bar x\sum_{i=1}^nx_i + n\bar x^2)
n1(i=1∑nxi2−2xˉi=1∑nxi+nxˉ2)
1
n
(
∑
i
=
1
n
x
i
2
−
2
n
x
ˉ
2
+
n
x
ˉ
2
)
\frac{1}{n}(\sum_{i=1}^nx_i^2-2n\bar x^2 + n\bar x^2)
n1(i=1∑nxi2−2nxˉ2+nxˉ2)
1
n
(
∑
i
=
1
n
x
i
2
−
n
x
ˉ
2
)
\frac{1}{n}(\sum_{i=1}^nx_i^2-n\bar x^2)
n1(i=1∑nxi2−nxˉ2)
1
n
∑
i
=
1
n
x
i
2
−
x
ˉ
2
\frac{1}{n}\sum_{i=1}^nx_i^2-\bar x^2
n1i=1∑nxi2−xˉ2
现在我们得到了两个部分,一个是平方的和,一个是和的平方。
因为和的平方只要我们知道和就可以了,所以选择记录平方和。
设
f
[
i
,
j
,
k
]
f[i,j,k]
f[i,j,k]表示走了i步,走到第j个点,路径权值和为k的总权值平方和。
然后分别枚举
i
,
j
,
k
,
l
i,j,k,l
i,j,k,l,表示从
l
l
l走到
j
j
j。
那如何转移呢?
从走了第
i
−
1
i-1
i−1条路,
l
l
l点,
k
−
e
[
l
.
d
i
s
]
k-e[l.dis]
k−e[l.dis]边权的平方和转移过来。
转移方程:
f
[
i
]
[
j
]
[
k
]
=
m
i
n
(
f
[
i
]
[
j
]
[
k
]
,
f
[
i
−
1
]
[
e
[
l
]
.
t
o
]
[
k
−
e
[
l
]
.
d
i
s
]
+
e
[
l
]
.
d
i
s
∗
e
[
l
]
.
d
i
s
)
;
f[i][j][k]=min(f[i][j][k],f[i-1][e[l].to][k-e[l].dis]+e[l].dis*e[l].dis);
f[i][j][k]=min(f[i][j][k],f[i−1][e[l].to][k−e[l].dis]+e[l].dis∗e[l].dis);
最后统计答案需要用方差公式:平方和/点数-和的平方/点的平方
取最小值
上代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<iomanip>
#include<cstring>
using namespace std;
struct lwx
{
int to,next,dis;
}e[301];
int n,m,dis[100][100];
int f[25][55][1100];//严格按照陈博(即数据)范围开数组!
/*设f[i,j,k]表示走了i步(最多20段),
走到d第j个点(最多50个点),
路径权值和为k(权值和最多会到1000)的总权值平方和*/
double ans;
int hd[301],tot;
void add(int x,int y,int z)
{
e[++tot].to=y;
e[tot].next=hd[x];
e[tot].dis=z;
hd[x]=tot;
}
int main()
{
freopen("library.in","r",stdin);
freopen("library.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=100;i++)
for(int j=1;j<=100;j++)
dis[i][j]=2147483647;
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(y,x,z);//倒着存边才好往前递推
}
memset(f,0x3f,sizeof(f));
f[0][1][0]=0;
for(int i=1;i<=20;i++)//题目规定20
{
for(int j=1;j<=n;j++)
{
for(int l=hd[j];l>0;l=e[l].next)//l一定在j前面(只能从下往上走)
{
for(int k=1;k<=1000;k++)//题目规定边权最多50,20*50=1000
{
if(k>=e[l].dis)
f[i][j][k]=min(f[i][j][k],f[i-1][e[l].to][k-e[l].dis]+e[l].dis*e[l].dis);
}
}
}
}
ans=15112717773.0;
for(int i=1;i<=20;i++)
{
for(int k=0;k<=1000;k++)
{
ans=min(ans,double(f[i][n][k])/i-(double(k*k))/(double(i*i)));
/*f[i,n,k]记录的是平方和,k是权值和,i是点的数量。
公式是:平方和/点数-和的平方/点的平方
和的平方/点的平方也就是平均数的平方*/
}
}
printf("%.4lf",ans);
return 0;
}