题目大意
现在有方程 f ( x ) = 6 ∗ x 7 + 8 ∗ x 6 + 7 ∗ x 3 + 5 ∗ x 2 − k ∗ x ( 0 ≤ x ≤ 100 ) f(x) = 6 * x^7+8*x^6+7*x^3+5*x^2-k*x (0 \leq x \leq 100) f(x)=6∗x7+8∗x6+7∗x3+5∗x2−k∗x(0≤x≤100),每次给出 k k k,求该函数在 [ 0 , 100 ] [0,100] [0,100]的最小值。
解题思路
方法一(二分/三分)
首先不难分析,在第一象限该函数一定是一个凹函数图像,那么很轻松就想到用三分去写。实际上还可以二分,对上述函数求导后,其导函数在第一象限一定是一个单调递增的直线,二分就是找导函数值为 0 0 0的 x x x。
//
// Created by Happig on 2020/11/9
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define ENDL "\n"
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 2e5 + 10;
double y;
double f(double x) {
return 6.0 * pow(x, 7.0) + 8.0 * pow(x, 6.0) + 7.0 * pow(x, 3.0) + 5.0 * pow(x, 2.0) - y * x;
}
double g(double x) {
return 42.0 * pow(x, 6.0) + 48.0 * pow(x, 5.0) + 21.0 * pow(x, 2.0) + 10.0 * x - y;
}
int dcmp(double d) {
if (fabs(d) < eps) return 0;
return d > 0 ? 1 : -1;
}
double tri_search() {
double l = 0, r = 100.0, midl, midr;
while (r - l > eps) {
midl = l + (r - l) / 3, midr = r - (r - l) / 3;
if (dcmp(f(midl) - f(midr)) < 0) {
r = midr;
} else l = midl;
}
return f(midl);
}
double bir_search() {
double l = 0, r = 100, mid;
while (l + eps < r) {
mid = (l + r) / 2;
if (dcmp(g(mid)) > 0) {
r = mid;
} else l = mid;
}
return f(l);
}
int main() {
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
//ios_base::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T;
scanf("%d", &T);
while (T--) {
scanf("%lf", &y);
//printf("%.4lf\n", bir_search());
printf("%.4lf\n", tri_search());
}
return 0;
}
方法二(爬山算法)
不少博客都说这样的写法是模拟退火,虽然设定了温度和阈值,但是实际上仍然是贪心求解问题,再加上这题的函数图像刚好是凸函数,于是不选择以一定概率接受更差的答案也得到了正确结果,那么模拟退火就退化到了爬山算法。
//
// Created by Happig on 2020/11/9
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define ENDL "\n"
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 2e5 + 10;
double y;
double f(double x) {
return 6.0 * pow(x, 7.0) + 8.0 * pow(x, 6.0) + 7.0 * pow(x, 3.0) + 5.0 * pow(x, 2.0) - y * x;
}
double g(double x) {
return 42.0 * pow(x, 6.0) + 48.0 * pow(x, 5.0) + 21.0 * pow(x, 2.0) + 10.0 * x - y;
}
int dcmp(double d) {
if (fabs(d) < eps) return 0;
return d > 0 ? 1 : -1;
}
double tri_search() {
double l = 0, r = 100.0, midl, midr;
while (r - l > eps) {
midl = l + (r - l) / 3, midr = r - (r - l) / 3;
if (dcmp(f(midl) - f(midr)) < 0) {
r = midr;
} else l = midl;
}
return f(midl);
}
double bir_search() {
double l = 0, r = 100, mid;
while (l + eps < r) {
mid = (l + r) / 2;
if (dcmp(g(mid)) > 0) {
r = mid;
} else l = mid;
}
return f(l);
}
int main() {
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
//ios_base::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T;
scanf("%d", &T);
while (T--) {
scanf("%lf", &y);
//printf("%.4lf\n", bir_search());
printf("%.4lf\n", tri_search());
}
return 0;
}
方法三(模拟退火)
经典的模拟退火模型:
t = 初始温度;
delta = 每次温度减少量;
eps = 最终停止的阈值;
ans = 答案, tmp = 可能最优状态;
while(t > eps) {
nxt = 从当前找到的可能最优状态随机找到的一个状态;
del = f(nxt) - f(ans);
if (del < 0)
ans = tmp = ans; //系统总能量最小已经达到最优
else if (exp(-del / t) * RAND_MAX > rand()) //实际上是rand()*1.0 / RAND_MAX,这里是减少浮点数计算误差
tmp = nxt; //大于当前最优解以一定概率取得然后保存尝试
t *= delta;
}
在本题需要注意的是,初始温度设置为
T
=
1
T=1
T=1,因为函数的范围只是
[
0
,
100
]
[0,100]
[0,100],因此我们在随机找状态时应该保证解是大于0的,于是下面这样写:while (nxt < 0) nxt = tmp + (rand() * 2.0 - RAND_MAX) * t;
//
// Created by Happig on 2020/11/9
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define ENDL "\n"
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-14;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 2e5 + 10;
const double T = 100;
const double delta = 0.996;
double y;
int dcmp(double d) {
if (fabs(d) < eps) return 0;
return d > 0 ? 1 : -1;
}
double f(double x) {
return 6.0 * pow(x, 7.0) + 8.0 * pow(x, 6.0) + 7.0 * pow(x, 3.0) + 5.0 * pow(x, 2.0) - y * x;
}
double SA() {
double t = T, ans = 50;
double tmp = ans;
while (t > eps) {
double nxt = tmp + (rand() * 2.0 - RAND_MAX) * t;
while (nxt < 0) nxt = tmp + (rand() * 2.0 - RAND_MAX) * t;
double del = f(nxt) - f(ans);
if (del < 0) {
ans = tmp = nxt;
} else if (exp(-del / t) * RAND_MAX > rand()) {
tmp = nxt;
}
t *= delta;
}
return ans;
}
int main() {
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
//ios_base::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T;
scanf("%d", &T);
while (T--) {
srand(100000007);
srand(rand()), srand(rand());
scanf("%lf", &y);
printf("%.4lf\n", f(SA()));
}
return 0;
}