【问题描述】 Nemo是一条无忧无虑的小鱼,它的初始体重为w0。可爱的Nemo希望自己能够尽快地成长,因此需要吃尽量多的食物。Nemo最喜爱的食物是海里的小虾。 已知Nemo对食物的情况了解如下:大海里共有n只小虾,从1到n编号,其中编号为i的小虾的重量为wi。将大海看作一个X-Y坐标系,在0时刻编号为i的小虾所在的位置为(xi, yi)。小虾在大海中作匀速直线运动,其中编号为i的小虾的速度向量为(pi, qi),即在时刻t,它的位置为 (xi+pi*t,yi+qi*t) Nemo在0时刻的位置为(x0, y0),它可以在海中随意移动,但速度不超过V。Nemo希望通过自己的努力,在T个单位时间内(含T时刻)吃到的小虾重量总和尽量大。当Nemo与某只小虾同时移动到同一个位置上,且小虾的重量小于Nemo当时的重量,则Nemo可以将该小虾吃掉。当Nemo吃掉重量为wi的小虾之后,它的体重将增加wi。注意,小虾不会吃Nemo,且小虾之间也不会自相残杀。 Nemo希望你来帮助它制定一个成长计划,使得它吃掉的小虾重量总和尽量大。 【输入格式】 该题为提交答案型试题,所有输入数据nemo1.in~nemo10.in已在试题目录下。 对于每个数据,输入文件中第一行为五个实数w0, V, T, x0, y0。分别表示Nemo的初始体重、最大移动速度、时间限制以及Nemo在0时刻的位置。 第二行为一个整数n,表示大海中小虾的只数。 接下来n行,每行5个实数,包括wi, xi, yi, pi, qi,分别表示编号为i的小虾的重量、在0时刻的位置和速度向量。 【输出格式】 针对给定的10 个输入文件nemo1.in~nemo10.in,你需要分别提交你的输出文件nemo1.out~nemo10.out。 输出文件第一行包含一个整数k。表示在你的成长计划中,Nemo将吃到k只小虾。 第二行包含一个实数w,表示在你的成长计划中,Nemo吃到的小虾的重量总和。 接下来k行,每行4个数t, x, y, s。表示在时刻t,Nemo在位置(x, y)处吃掉了编号为s的小虾。其中t, x, y为实数,s为整数。 为保证验证程序的精度,所有实数建议至少输出到小数点后6位。在验证程序中,两个实数绝对误差不超过10-4时,即视为相等。 【样例输入】 5 1 6 0 0 1 5 2 2 0 0 【样例输出】 1 5 5 2 2 1 【样例说明】 在这个样例中,Nemo在时刻5在位置(2, 2)吃掉了1号小虾。其实Nemo到达(2, 2)的时间可以更早,但题中仅要求速度不超过V即可。 【评分方法】 对于每组数据,我们设置了9个评分参数a10,a9,…,a2。如果选手的输出不合法,则得零分。否则,设在你的方案中,Nemo体重的增加量为wuser,你的分数将会由下表给出:
如果有多项满足,则取满足条件中的最高得分。
通法:随机化贪心,并且加上各种优化。
先说计算出从A出发,追赶一个当前位置为B,以速度v移动的小虾所需时间的方法如下。
如图所示,根据余弦定理,有:
即:
也即:
只需要用求根公式求解即可(取两根中的较小正根)。
朴素的随机化贪心算法如下。
先将所有的小虾按体重从小到大排序。
每次将所有小虾按照单位时间的收益(增重比上时间)从大到小排序,
选取一个最优的小虾,或者以一定概率接受更差的小虾(并迭代进行,即再以一定概率接受更差的小虾),这个概率可以是0.5,或1/e。
优化1:贪心多次,取最小值;
优化2:对更差的解进行限制(如果太差就直接舍去);
优化3:提高时间所占的比重(按增重比上时间的平方从大到小排序);
优化4:引入模拟退火的思想,即使遇到非常差的解,仍然以一定概率接受它(这个概率跟温度有关,每贪心一次就按照一定规律降温(如将温度减半))。
加上这些优化基本上就可以得全分了。
附程序:
#include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <string> #include <vector> #include <complex> #include <ctime> #include <cmath> using namespace std; const int maxN = 10010; const double zero = 1e-12, INF = 1e198; struct Res { double t; complex <double> p; int ord; Res() {} Res(double t, complex <double> p, int ord): t(t), p(p), ord(ord) {} } res[maxN], _res[maxN]; bool eaten[maxN]; complex <double> p[maxN], v[maxN]; double w[maxN], V, _deltaw, deltaw, T, tem; vector <pair <double, int> > ls; vector <pair <double, int> >::iterator iter; int ord[maxN], n, cnt, _cnt; inline bool cmp(const int &a, const int &b) {return w[a] < w[b];} inline double sqr(double x) {return x * x;} inline double sqr(complex <double> z) {return sqr(real(z)) + sqr(imag(z));} inline double solve(complex <double> p, complex <double> v, complex <double> O) { double a = sqr(v) - sqr(V), b = -2 * (real(v) * real(O - p) + imag(v) * imag(O - p)), c = sqr(p - O), delta = sqr(b) - 4 * a * c; if (delta < -zero) return INF; if (fabs(a) < zero) return (b > -zero) ? INF : (-c / b); double ans = INF, x1 = (-b - sqrt(delta)) / (2 * a), x2 = (-b + sqrt(delta)) / (2 * a); if (x1 > -zero) ans = x1; if (x2 > -zero && x2 < ans) ans = x2; return ans; } //计算出从O出发,追赶一个当前位置为p,以速度v移动的小虾所需时间。 inline double _rand() {return (double)rand() / RAND_MAX;} inline bool judge() { if (++iter == ls.end()) {--iter; return 0;} if (rand() & 1) {--iter; return 0;} //先以0.5的概率初步淘汰较差的解。 double nxt = iter -> first; --iter; double ths = iter -> first; double delta = (ths - nxt); if (delta / ths < 3.40e-2) return 1; //若较差的解比当前解差3.4%以上,直接淘汰。 return _rand() < 1 / exp(delta / tem); //否则以概率e ^ (-delta / tem)的概率接受更差的解。 } int main() { freopen("nemo10.in", "r", stdin); freopen("nemo10.out", "w", stdout); srand(time(NULL)); scanf("%lf%lf%lf%lf%lf%d", w + 0, &V, &T, &p[0].real(), &p[0].imag(), &n); complex <double> p0 = p[0]; double w0 = w[0]; for (int i = 1; i < n + 1; ord[i] = i, ++i) scanf("%lf%lf%lf%lf%lf", w + i, &p[i].real(), &p[i].imag(), &v[i].real(), &v[i].imag()); sort(ord + 1, ord + n + 1, cmp); //将小虾按体重从小到大的顺序排序。 tem = 1; //初始温度设为1。 for (int k = 0; k < 15; ++k) { memset(eaten, 0, sizeof eaten); _deltaw = 0; _cnt = 0; p[0] = p0; w[0] = w0; for (double t = 0; t < T;) { ls.clear(); for (int i = 1; w[ord[i]] < w[0]; ++i) if (!eaten[ord[i]]) ls.push_back(make_pair(w[ord[i]] / sqr(solve(p[ord[i]] + t * v[ord[i]], v[ord[i]], p[0])), ord[i])); sort(ls.begin(), ls.end(), greater <pair <double, int> > ()); iter = ls.begin(); if (iter == ls.end()) break; while (judge()) ++iter; int pos = iter -> second; if (T - (t += solve(p[pos] + t * v[pos], v[pos], p[0])) < -zero) break; _deltaw += w[pos], w[0] += w[pos]; eaten[pos] = 1; _res[_cnt++] = Res(t, p[0] = p[pos] + t * v[pos], pos); } if (_deltaw > deltaw) { deltaw = _deltaw; for (cnt = 0; cnt < _cnt; ++cnt) res[cnt] = _res[cnt]; } //更新最优解。 tem *= .5; } printf("%d\n%.9lf\n", cnt, deltaw); for (int i = 0; i < cnt; ++i) printf("%.9lf %.9lf %.9lf %d\n", res[i].t, res[i].p.real(), res[i].p.imag(), res[i].ord); return 0; }
对于不同的数据,还可以用特殊方法求解。
第一组数据显然手算,不多说。
第二组数据较小,可以暴力枚举,附程序:
#include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <string> #include <complex> using namespace std; const int maxN = 10010; const double zero = 1e-12, INF = 1e198; struct Res { double t; complex <double> p; int ord; Res() {} Res(double t, complex <double> p, int ord): t(t), p(p), ord(ord) {} } res[maxN], tmp[maxN]; bool eaten[maxN]; complex <double> p[maxN], v[maxN]; double w[maxN], V, deltaw, T; int n, cnt; inline double sqr(double x) {return x * x;} inline double sqr(complex <double> z) {return sqr(real(z)) + sqr(imag(z));} inline double solve(complex <double> p, complex <double> v, complex <double> O) { double a = sqr(v) - sqr(V), b = -2 * (real(v) * real(O - p) + imag(v) * imag(O - p)), c = sqr(p - O), delta = sqr(b) - 4 * a * c; if (delta < -zero) return INF; if (fabs(a) < zero) return (b > -zero) ? INF : (-c / b); double ans = INF, x1 = (-b - sqrt(delta)) / (2 * a), x2 = (-b + sqrt(delta)) / (2 * a); if (x1 > -zero) ans = x1; if (x2 > -zero && x2 < ans) ans = x2; return ans; } void Dfs(int i, double w0, double t, complex <double> p0) { for (int j = 1; j < n + 1; ++j) if (!eaten[j] && w[j] < w0) { double ths = solve(p[j] + t * v[j], v[j], p0); if (t + ths - T > zero) continue; complex <double> pos = p[j] + (t + ths) * v[j]; eaten[j] = 1; tmp[i] = Res(t + ths, pos, j); Dfs(i + 1, w0 + w[j], t + ths, pos); tmp[i] = Res(0, 0, 0); eaten[j] = 0; } if (w0 - w[0] > deltaw) { deltaw = w0 - w[0]; for (cnt = 0; tmp[cnt].t > zero; ++cnt) res[cnt] = tmp[cnt]; } return; } int main() { freopen("nemo2.in", "r", stdin); freopen("nemo2.out", "w", stdout); scanf("%lf%lf%lf%lf%lf%d", w + 0, &V, &T, &p[0].real(), &p[0].imag(), &n); for (int i = 1; i < n + 1; ++i) scanf("%lf%lf%lf%lf%lf", w + i, &p[i].real(), &p[i].imag(), &v[i].real(), &v[i].imag()); Dfs(0, w[0], 0, p[0]); printf("%d\n%.9lf\n", cnt, deltaw); for (int i = 0; i < cnt; ++i) printf("%.9lf %.9lf %.9lf %d\n", res[i].t, res[i].p.real(), res[i].p.imag(), res[i].ord); return 0; }
#include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <string> #include <complex> using namespace std; const int maxN = 10010; const double zero = 1e-12, INF = 1e198; struct Res { double t; complex <double> p; int ord; Res() {} Res(double t, complex <double> p, int ord): t(t), p(p), ord(ord) {} } res[maxN]; bool eaten[maxN]; complex <double> p[maxN], v[maxN]; double w[maxN], V, deltaw, T; int ord[maxN], n, cnt; inline bool cmp(const int &a, const int &b) {return w[a] < w[b];} inline double sqr(double x) {return x * x;} inline double sqr(complex <double> z) {return sqr(real(z)) + sqr(imag(z));} inline double solve(complex <double> p, complex <double> v, complex <double> O) { double a = sqr(v) - sqr(V), b = -2 * (real(v) * real(O - p) + imag(v) * imag(O - p)), c = sqr(p - O), delta = sqr(b) - 4 * a * c; if (delta < -zero) return INF; if (fabs(a) < zero) return (b > -zero) ? INF : (-c / b); double ans = INF, x1 = (-b - sqrt(delta)) / (2 * a), x2 = (-b + sqrt(delta)) / (2 * a); if (x1 > -zero) ans = x1; if (x2 > -zero && x2 < ans) ans = x2; return ans; } int main() { freopen("nemo3.in", "r", stdin); freopen("nemo3.out", "w", stdout); scanf("%lf%lf%lf%lf%lf%d", w + 0, &V, &T, &p[0].real(), &p[0].imag(), &n); for (int i = 1; i < n + 1; ord[i] = i, ++i) scanf("%lf%lf%lf%lf%lf", w + i, &p[i].real(), &p[i].imag(), &v[i].real(), &v[i].imag()); sort(ord + 1, ord + n + 1, cmp); for (double t = 0; T - t > zero;) { static int i = 0; ++i; if (i > n) break; double Min = solve(p[ord[i]] + t * v[ord[i]], v[ord[i]], p[0]); int pos = ord[i]; if (fabs(Min - INF) < zero) break; if (T - (t += Min) < -zero) break; w[0] += w[pos]; deltaw += w[pos]; eaten[pos] = 1; res[cnt++] = Res(t, p[0] = p[pos] + t * v[pos], pos); } printf("%d\n%.9lf\n", cnt, deltaw); for (int i = 0; i < cnt; ++i) printf("%.9lf %.9lf %.9lf %d\n", res[i].t, res[i].p.real(), res[i].p.imag(), res[i].ord); return 0; }第四组数据中所有小虾静止不动且在一条直线上,据说是区间动态规划,方法至今未知。
第五、六组数据中所有小虾都在高速向下掉,而Nemo的移动速度很小,可以近似看作Nemo在只能水平移动的情况下去接住小虾来吃。
先将小虾按竖直方向排序,然后进行一下操作。
设f[i]表示前i个小虾中恰好第i个被吃所能得到的最大收益,只需要一个n^2的动态规划即可求解。
附程序:
#include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <string> #include <complex> using namespace std; const int maxN = 10010; const double zero = 1e-12, INF = 1e198; struct Res { double t; complex <double> p; int ord; Res() {} Res(double t, complex <double> p, int ord): t(t), p(p), ord(ord) {} } res[maxN]; bool eaten[maxN]; complex <double> p[maxN], v[maxN]; double f[maxN], w[maxN], V, deltaw, T; int ord[maxN], g[maxN], n, cnt; inline bool cmp(const int &a, const int &b) {return imag(p[a]) < imag(p[b]);} void calc(int i) { if (g[i]) calc(g[i]); res[cnt++] = Res(imag(p[i]) / 1000, p[i], i); deltaw += w[i]; return; } int main() { freopen("nemo5.in", "r", stdin); freopen("nemo5.out", "w", stdout); scanf("%lf%lf%lf%lf%lf%d", w + 0, &V, &T, &p[0].real(), &p[0].imag(), &n); for (int i = 1; i < n + 1; ord[i] = i, ++i) scanf("%lf%lf%lf%lf%lf", w + i, &p[i].real(), &p[i].imag(), &v[i].real(), &v[i].imag()); sort(ord + 1, ord + n + 1, cmp); int st = 1; while (imag(p[ord[st]]) / 1000 - real(p[ord[st]]) / V < -zero) ++st; f[ord[st]] = w[ord[st]]; for (int i = 1; i < st; ++i) f[ord[i]] = -INF; for (int i = 0; i < n; ++i) if (f[ord[i]] > -INF) for (int j = i + 1; j < n + 1; ++j) { if (imag(p[ord[j]] - p[ord[i]]) / 1000 - fabs(real(p[ord[i]] - p[ord[j]])) / V > -zero) if (f[ord[i]] + w[ord[j]] > f[ord[j]]) f[ord[j]] = f[ord[i]] + w[ord[j]], g[ord[j]] = ord[i]; } int pos = ord[n]; for (int i = 1; i < n + 1; ++i) if (f[i] > f[pos]) pos = i; calc(pos); printf("%d\n%.9lf\n", cnt, deltaw); for (int i = 0; i < cnt; ++i) printf("%.9lf %.9lf %.9lf %d\n", res[i].t, res[i].p.real(), 0., res[i].ord); return 0; }第七、八组数据中可以发现小虾的位置呈现数字三角形的形状,只需要按数字三角形的方法进行动态规划即可。
附程序:
#include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <string> #include <complex> using namespace std; const int maxN = 2010, INF = 0x3f3f3f3f; const double zero = 1e-12; struct Res { double t; complex <int> p; int ord; Res() {} Res(double t, complex <int> p, int ord): t(t), p(p), ord(ord) {} } res[maxN], tmp[maxN]; bool eaten[maxN]; complex <int> p[maxN], v[maxN]; double w[maxN], deltaw, V, T, f[maxN][maxN]; int ord[maxN][maxN], g[maxN], n, cnt; inline double sqr(double x) {return x * x;} inline double sqr(complex <double> z) {return sqr(real(z)) + sqr(imag(z));} int main() { freopen("nemo8.in", "r", stdin); freopen("nemo8.out", "w", stdout); scanf("%lf%lf%lf%d%d%d", w + 0, &V, &T, &p[0].real(), &p[0].imag(), &n); for (int i = 1; i < n + 1; ++i) scanf("%lf%d%d%d%d", w + i, &p[i].real(), &p[i].imag(), &v[i].real(), &v[i].imag()); for (int i = 1; i < n + 1; ++i) ord[real(p[i])][imag(p[i])] = i; int i = n; for (; i; --i) { f[real(p[i])][imag(p[i])] = w[i]; if (imag(p[i - 1]) < imag(p[i])) break; } while (--i) { int x = real(p[i]), y = imag(p[i]), ths = ord[x][y]; if (f[x - 1][y + 1] > f[x + 1][y + 1]) { f[x][y] = w[i] + f[x - 1][y + 1]; g[ths] = ord[x - 1][y + 1]; } else { f[x][y] = w[i] + f[x + 1][y + 1]; g[ths] = ord[x + 1][y + 1]; } } double t = 0; res[cnt++] = Res(t = abs(p[1] - p[0]) / V, p[1], 1); for (int ths = 1; g[ths]; ths = g[ths]) res[cnt++] = Res(t += abs(complex <double> (real(p[g[ths]] - p[ths]), imag(p[g[ths]] - p[ths]))) / V, p[g[ths]], g[ths]); for (int i = 0; i < cnt; ++i) deltaw += w[res[i].ord]; printf("%d\n%lf\n", cnt, deltaw); for (int i = 0; i < cnt; ++i) printf("%.9lf %d %d %d\n", res[i].t, res[i].p.real(), res[i].p.imag(), res[i].ord); return 0; }第九组数据中所有小虾都静止不动,简单的贪心就可以过。
第十组数据可能就只有随机化贪心能够解决了……