3528. 【NOIP2013模擬11.7A組】圖書館(library)
阿新 • • 發佈:2021-07-18
DP
,序列的數字個數 \(\leq 20\),而總點數也 \(\leq 50\)。
。
給定一張有 \(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\)
所以最小化方差,完全可以列舉所有的 序列和 和 序列數字個數,嘗試最小化平方和來得到答案。
設 \(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; }