3528. 【NOIP2013模拟11.7A组】图书馆(library)

图书馆

给定一张有 \(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 \]

它相比于方差的定义式的好处是,将需要求解的量分成了多个易于求解的部分:

  1. 序列的平方和。
  2. 序列的和。
  3. 序列的数字个数。

回到题目,分析数据范围可知,序列的和 \(\leq 20\times 50=1000\),序列的数字个数 \(\leq 20\),而总点数也 \(\leq 50\)

所以最小化方差,完全可以枚举所有的 序列和 和 序列数字个数,尝试最小化平方和来得到答案。

\(f(i,j,k)\) 表示到达 \(i\) 号平台,走过 \(j\) 个台阶,道路总长为 \(k\),道路的最小平方和。

  1. 初始化:\(f(1,0,0)=0,others=\inf\)
  2. 转移:有边 \((u,v,w)\),则有 \(f(u,j,k)\to f(v,j+1,k+w)+w^2\)
  3. 总答案:\((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;
}
posted @ 2021-07-18 10:08  LPF'sBlog  阅读(44)  评论(0编辑  收藏  举报