题目本身是好题
题目描述 Description
学校科技楼一共有N层,而神犇YJQ每天都在科技楼N楼的机房写代码。这天,他准备从科技楼1楼爬到N楼。有个M连接不同楼层的楼梯,爬每个楼梯需要一定的体力值。楼梯一定是从低处通往高处的。(但是由于楼房的设计比较奇怪,第i楼并不一定在第i-1楼上面,也就是说给出的边不保证x<y,但保证图为DAG,请自行处理楼层之间的高度关系)。为了省时间,YJQ一定只会上楼梯而不会下楼梯,即楼梯间不会形成环路。而且出于人性化考虑,不管YJQ选择什么路线上楼,他爬的楼梯数量一定小于20。为了使体力消耗尽量平稳,YJQ需要选择一条“每个楼梯消耗体力值的方差最小”的路径上楼。请帮助YJQ计算出这个最小方差。
输入描述 Input Description
第一行包含2个整数N,M表示科技楼楼层数和楼梯数; 接下来M行,每行3个数,x,y,z表示存在一条由x层通往平台y层的楼梯,爬这个楼梯需要消耗z的体力值。
输出描述 Output Description
一行1个实数,表示最小方差,精确到小数点后4位。
样例输入 Sample Input
4 4 1 2 1 2 4 3 1 3 2 3 4 3
样例输出 Sample Output
0.2500
数据范围及提示 Data Size & Hint
对于30%的数据,N<=10,M<=20;
另有20%的数据N<=35,M<=220,Z∈0,1;
对于100%的数据2<=N<=50,M<=300,0<=Z<=50保证至少存在一条由1到N的路径。
Solution
我们考虑方差的计算公式
(图丢了QAQ)
所以我们可以考虑动态规划,dp[i][j][k]表示走到i号点,走了j条边并且边权和为k的最小边权平方和,最终的答案是走到i号点时枚举一下走了多少条边与边权和为多少,并以此计算答案。可以证明最小方差一定在所有的状态之中
复杂度:O(202050*M)(20是最大边数,50是每条边的最大权值)
这个处理方差的做法是可以推广的
实际是绝对方差值的有三个因素
- 元素个数
- 元素和
- 元素平方和
本题的思想就是控制其中的两个变量,另剩余的一个变量最小。
经过美化后的代码(visual studio最强功能 ctrl+k,ctrl+d 格式化代码)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using namespace std;
struct Data {
int v, w, nex;
} data[MAXM]; int fir[MAXN];
int e = 0;
void addedge(int x, int y, int z) {
data[++e].v = y;
data[e].w = z;
data[e].nex = fir[x];
fir[x] = e;
}
int q[10000], h = 0, t = 0;
int U[MAXN], cnt = 0;
int inq[MAXN];
int f[MAXN][MAXdep][50 * MAXM];
int n, m, sum = 0;
void topo() {
memset(f, 127, sizeof f);
const int inf = f[1][0][0];
f[1][0][0] = 0;
for (int i = 1; i <= n; i++) if (!inq[i]) q[++t] = i;
while (h < t) {
int u = q[++h];
for (int j = 0; j <= 20; j++)
for (int k = 0; k <= sum; k++)
if (f[u][j][k] != inf)
for (int p = fir[u]; p > 0; p = data[p].nex) {
int v = data[p].v, w = data[p].w;
if (j + 1 <= 20 && w + k <= sum)
f[v][j + 1][w + k] = min(f[v][j + 1][w + k], f[u][j][k] + w*w);
}
for (int i = fir[u]; i; i = data[i].nex) {
int v = data[i].v;
if (!--inq[v])
q[++t] = v;
}
}
}
int main() {
freopen(""1.in"", ""r"", stdin);
freopen(""1.out"", ""w"", stdout);
scanf(""%d%d"", &n, &m);
for (int i = 1; i <= m; i++) {
int x, y, z;
scanf(""%d%d%d"", &x, &y, &z);
addedge(x, y, z);
inq[y]++;
sum += z;
}
topo();
double ans = INF;
for (int j = 1; j <= 20; j++)
for (int k = 0; k <= sum; k++)
ans = min(ans, ((double)f[n][j][k] / (double)j) - (double)(k*k) / (double)(j*j));
printf(""%.4f"", ans);
fclose(stdin); fclose(stdout);
return 0;
}
My Story
- 一开始maxn写成40了orz
- 发现maxm也写错了
- 改了还不对
- 发现输出有负数。。。。
- 猛然发现需要拓扑排序。。。
- 拓扑完还错
- 拓扑打错了 应该一开始把所有入度为0的都入队,支线也会对主线有影响
- 还是错
- 无奈造数据。。
- 数据造太差 人工调
- 发现负数
- 调试的时候发现当程序吧数组的某一个值赋值时,数组中的别的元素居然有干扰。。。
举个例子 赋值f[5][1][10]=100后, f[5][0][810]的值也变成了100 - 对此玄学陷入深深的惶恐。。。
- 发现如果不用宏定义就好了 即int f[maxn][maxn][maxm*50] 改成普通形式
- ac了
- 发现用宏定义只要数组不开大也没事。
- 得出结论c++果然是玄学
- 教训:以后数组千万别乱开啊啊啊
“