给定一张有 \(n\) 个点的 DAG,求边权的方差最小的 \(1\sim n\) 的路径,保证最长的路径不会经过超过 \(20\) 条边。
\(n\leq 50,w\leq 50\),其中 \(w\) 表示单个边的边权。
太菜了不会方差公式,所以根本无从下手。先给出公式:
\[S^2=((a_1^2+a_2^2+\cdots+a_n^2)-(a_1+a_2+\cdots+a_n)^2/n)/n \]它相比于方差的定义式的好处是,将需要求解的量分成了多个易于求解的部分:
- 序列的平方和。
- 序列的和。
- 序列的数字个数。
回到题目,分析数据范围可知,序列的和 \(\leq 20\times 50=1000\),序列的数字个数 \(\leq 20\),而总点数也 \(\leq 50\)。
所以最小化方差,完全可以枚举所有的 序列和 和 序列数字个数,尝试最小化平方和来得到答案。
设 \(f(i,j,k)\) 表示到达 \(i\) 号平台,走过 \(j\) 个台阶,道路总长为 \(k\),道路的最小平方和。
- 初始化:\(f(1,0,0)=0,others=\inf\)。
- 转移:有边 \((u,v,w)\),则有 \(f(u,j,k)\to f(v,j+1,k+w)+w^2\)。
- 总答案:\((f(n,j,k)-k^2/j)/j\)。
然后就完美解决了这道题,最坏时间复杂度 \(O(n\times 20\times 1000)\)。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define MP make_pair
const int N = 60, M = 310;
const int INF = 0x3f3f3f3f;
int n, m;
int f[N][30][1010];
vector<pair<int, int> > G[N];
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int main(){
freopen("library.in", "r", stdin);
freopen("library.out", "w", stdout);
n = read(), m = read();
for(int i = 1; i <= m; i ++){
int u = read(), v = read(), w = read();
G[u].push_back(MP(v, w));
}
memset(f, 0x3f, sizeof(f));
f[1][0][0] = 0;
for(int j = 0; j < 20; j ++)
for(int u = 1; u <= n; u ++)
for(int k = 0; k <= 1000; k ++) if(f[u][j][k] != INF)
for(int i = 0; i < (int) G[u].size(); i ++){
int v = G[u][i].first, w = G[u][i].second;
f[v][j + 1][k + w] = min(f[v][j + 1][k + w], f[u][j][k] + w * w);
}
double ans = 1e9;
for(int j = 1; j < 20; j ++)
for(int k = 0; k <= 1000; k ++) if(f[n][j][k] != INF)
ans = min(ans, (double)(f[n][j][k] - (double)k * k / j) / j);
printf("%.4lf\n", ans);
return 0;
}