5 缺点和不足
在扔掉那些必须要扔的药的时候,我是选择尽量便宜的几种药丢弃,这样做在绝大多数情况下都是很好的。但在构造数据下,可能会出现扔掉较贵的药反而获利更大的情况。这也是我的程序中的唯一纰漏,也是其将最优解排除在解空间外的唯一情况。综合考虑随机数据下这种情况微乎其微的概率以及完善它所需要的代码量,没有选择去修补它。
6 总结与感悟
从结果看,运行多次程序结果相同,这令我非常吃惊,因为我的程序里有随机化成分,在我预想中最终结果应该有微小浮动,至少不会总是一样。对此有两种可能的解释:一种解释是这个解就是全局最优解,自然不会有更好的解出现;另一种解释是这是局部最优解而全局最优解极为刁钻,甚至不能近其身。我个人倾向于第一种。因为每多一种药过期在我的解法里相当于多了一条限制,会帮助我缩小解空间。而一开始下发的data1中没有药过期,理论上这种情况下我的算法找到最优解的概率最小,但它仅用很短的时间就找到了一个解,并且通过手算可以证明它就是最优解。
当然,这也无法完全验证或者证伪,因为目前还没有别人跑出更优的解。究其原因,可能是2.3中给出的解空间上界确实非常非常松,实际数据下解空间大小远没有这么大,从而比较容易找到最优解;也有可能是最优解距离初始贪心解的距离不太远。
回过头来看,整个算法最为关键的部分是前面对于压缩解空间的分析,后面的模拟退火反而没那么重要,只是顺势而为罢了,或者只能算一种妥协。事实上,我把模拟退火的部分去掉后,直接从初始贪心解周围随机若干解的效果也不错,甚至也超过了教辅的收益。将随机换成直接爬山效果更佳,距离模拟退火退出来的结果只有几元钱的差距。
最终的 sim.cpp 和 s1.cpp 加上实验报告总共花了三个晚上的时间,比预期短,是因为我吸取了银行的教训,没有过于自信直接上手而花费很多时间在不可行的方案上。这次我在前期花了大量的时间准备思考,写了大量测试数据的代码(虽然这部分最后没派上用场),全部考虑清楚后再着手写代码,避免了很多无用功,也让我的思路更加清楚,代码一气呵成,没花多少时间调试。
/**
* TODO:
*
*
* */
# include <bits/stdc++.h>
# include <getopt.h>
# define pb push_back
# define ll long long
using namespace std;
const double deltav[] = {-1.5, -1, -0.5, 0, 2, 4, 6}; // 定价表
struct node {
double v;
int t;
} a[100];
string fpath = "testdata\\data"; // 路径,测试用
// 策略结构体,包含strategy和delete
struct Koutput {
int s[11][11][2], d[51][2], n;
Koutput() { n = 0; }
Koutput& operator=(const Koutput &b) {
memcpy(s, b.s, sizeof(s));
memcpy(d, b.d, sizeof(d));
n = b.n;
return *this;
}
void print() {
ofstream fout;
fout.open((fpath + "\\strategy.my").c_str());
for (int i = 1; i <= 10; i++) {
for (int j = 0; j < 10; j++) {
fout << s[i][j][0] << ',' << s[i][j][1] << ' ';
}
fout << '\n';
}
fout.close();
fout.open((fpath + "\\delete.my").c_str());
for (int i = 0; i < n; i++) {
fout << d[i][0] << ' ' << d[i][1] <<'\n';
}
fout.close();
}
} beststr;
// 退火用到的3*10的数表结构体
struct SAnode{
int A[11][3], B[30], n;
SAnode(): n(0) {}
} initstate, bestSAstr;
int n = 50; // 药品总数
int s_a[100]; // 过期时间升的药品编号
int initDel[100], initDelcnt; // 一开始扔掉哪些药及其数量
double bestans = -10000; // 最大获利
double initCost = 0; // 扔药的损失
vector<int> ksj1, ksj2; // 计算获利时维护的集合,ksj1是即将过期的药,ksj2是其余药
bool cmp_greater(int x, int y) { return a[x].v > a[y].v; }
bool cmp_id(int x, int y) {
if (a[x].t ^ a[y].t) return a[x].t < a[y].t;
return a[x].v > a[y].v;
}
// 生成[0,1]浮点数
double frand() { return 1.0 * rand() / RAND_MAX; }
// 解析命令行参数并读入
void read(int argc, char* argv[]) {
if (argc == 1) {
int tmp;
scanf("%d", &tmp);
fpath = fpath + to_string(tmp);
FILE* fp = fopen((fpath + "\\medicine.txt").c_str(), "r");
for (int i = 0; i < n; i++) {
fscanf(fp, "%lf%d", &a[i].v, &a[i].t);
s_a[i] = i;
}
fclose(fp);
return;
}
int opt;
const char* optstring = "m:s:d:";
while ((opt = getopt(argc, argv, optstring)) != -1) {
FILE* fp = fopen(optarg, "r");
if (opt == 'm') {
for (int i = 0; i < n; i++) {
fscanf(fp, "%lf%d", &a[i].v, &a[i].t);
s_a[i] = i;
}
}
fclose(fp);
}
}
// 计算退火解的盈利
double calculate(SAnode s) {
Koutput str;
double profit = 0;
ksj1.clear(), ksj2.clear();
for (int i = 0; i < s.n; i++) {
int u = s.B[i];
if (a[u].t <= 10) str.d[str.n][0] = 0, str.d[str.n][1] = u, str.n++, profit -= a[u].v;
else ksj2.pb(u);
}
// 初始化
for (int i = 1; i <= 10; i++) {
for (int j = 0; j < 3; j++) {
if (a[s.A[i][j]].t <= 5) ksj1.pb(s.A[i][j]); //=
else ksj2.pb(s.A[i][j]);
}
}
vector<int>::iterator it;
// 开始遍历每一天
for (int i = 1; i <= 10; i++) {
sort(s.A[i], s.A[i] + 3, cmp_greater);
double maxv = a[s.A[i][0]].v;
// 把临近过期的药转移到 ksj1
for (it = ksj2.begin(); it != ksj2.end(); ) {
int u = *it;
if (a[u].t - i + 1 <= 5) ksj1.pb(u), it = ksj2.erase(it); //=
else it++;
}
// 上架卖的三种药
for (int j = 0; j < 3; j++) {
int u = s.A[i][j];
str.s[i][j][0] = u;
if (a[u].t - i + 1 <= 5) ksj1.erase(find(ksj1.begin(), ksj1.end(), u)); //=
else ksj2.erase(find(ksj2.begin(), ksj2.end(), u));
}
sort(ksj1.begin(), ksj1.end(), cmp_greater);
sort(ksj2.begin(), ksj2.end(), cmp_greater);
profit -= ksj1.size() + ksj2.size() * 0.5; // 先减去所有药的管理费,这样可以把上架药品的管理费看做收益
int p = 0, q = 0;
int k0 = 6, k1 = 6, k2 = 6, u = s.A[i][0], v = s.A[i][1], w = s.A[i][2];
int maxk0, maxk1, maxk2, maxp, maxq;
double Max = -10000;
// 枚举卖出药品的定价不大于 maxv+k,计算此时当天收益(卖药利润+上架不卖的7件药品的管理费)
for (int k = 6; k >= -1; k--) {
while (p < ksj1.size() && a[ksj1[p]].v + 6 > maxv + k) p++;
while (q < ksj2.size() && a[ksj2[q]].v + 6 > maxv + k) q++;
while (a[u].v + deltav[k0] > maxv + k) k0--;
while (a[v].v + deltav[k1] > maxv + k) k1--;
while (a[w].v + deltav[k2] > maxv + k) k2--;
double tmp = deltav[k0] + deltav[k1] + deltav[k2] + min(p, 7) + 0.5 * min(q, 7 - min(p, 7));
if (tmp > Max) {
Max = tmp;
maxp = p, maxq = q;
maxk0 = k0, maxk1 = k1, maxk2 = k2;
}
}
int t = 0;
str.s[i][0][1] = maxk0;
str.s[i][1][1] = maxk1;
str.s[i][2][1] = maxk2;
while (t < 7 && maxp) str.s[i][3 + t][0] = ksj1[--maxp], str.s[i][3 + t][1] = 6, t++;
while (t < 7 && maxq) str.s[i][3 + t][0] = ksj2[--maxq], str.s[i][3 + t][1] = 6, t++;
while (t < 7) str.s[i][3 + t][0] = -1, str.s[i][3 + t][1] = 6, t++;
profit += Max;
}
if (profit > bestans) {
bestans = profit;
beststr = str;
bestSAstr = s;
}
return profit;
}
// 调整退火解
SAnode adjust(SAnode s) {
for (int i = 0; i <= rand() % 5; i++) {
while (1) {
int opt = rand() % 4;
if (opt == 0) {
int x = rand() % 10 + 1, y = rand() % 3, z = rand() % s.n;
if (a[s.B[z]].t >= x && a[s.A[x][y]].t > 10) {
swap(s.B[z], s.A[x][y]);
break;
}
} else {
int x = rand() % 10 + 1, y = rand() % 3;
int u = rand() % 10 + 1, v = rand() % 3;
if (a[s.A[x][y]].t >= u && a[s.A[u][v]].t >= x) {
swap(s.A[x][y], s.A[u][v]);
break;
}
}
}
}
return s;
}
// 贪心生成初始解
SAnode greedy() {
int p = 0;
SAnode ret;
for (int i = 1; i <= 10; i++) {
for (int j = 0; j < 3; j++) {
while (a[s_a[p]].t < i) {
int u = s_a[p];
for (int ii = 1; ii <= a[u].t; ii++) {
for (int jj = 0; jj < 3; jj++) {
if (a[ret.A[ii][jj]].v < a[u].v) swap(u, ret.A[ii][jj]);
}
}
initDel[initDelcnt++] = u;
initCost += a[u].v;
p++;
}
ret.A[i][j] = s_a[p++];
}
}
while (p < 50) {
ret.B[ret.n++] = s_a[p++];
}
return ret;
}
// 进行模拟退火
double SA() {
double T = 10000, alpha = 0.99, lst = -10000;
SAnode state = initstate;
lst = calculate(state);
while (T > 0.000001) {
SAnode newState = adjust(state);
double tmp = calculate(newState), delta = tmp - lst;
if (delta > 0 || exp(delta/T) > frand()) lst = tmp, state = newState;
T *= alpha;
}
return lst;
}
int main(int argc, char* argv[]) {
srand(time(0) + 9927);
//cout<<"#";
read(argc, argv);
sort(s_a, s_a + n, cmp_id);
initstate = greedy();
for (int i = 1; i <= 300; i++) SA();
// 在所得解附近爬山
SAnode state = bestSAstr;
double lst = bestans;
for (int i = 1; i <= 5000; i++) {
SAnode newState = adjust(state);
double tmp = calculate(newState);
if (tmp > lst) {
lst = tmp;
state = newState;
}
}
for (int i = 0; i < initDelcnt; i++) beststr.d[beststr.n][0] = 0, beststr.d[beststr.n][1] = initDel[i], beststr.n++;
beststr.print();
cout << bestans - initCost;
return 0;
}
/* by Hellsegamosken */
————————————————
本文转自CSDN平台博主:Hellsegamosken
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/DT_Kang/article/details/122316269