1. 程式人生 > 其它 >3528. 【NOIP2013模擬11.7A組】圖書館(library)

3528. 【NOIP2013模擬11.7A組】圖書館(library)

DP

圖書館

給定一張有 \(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;
}