HDU - 2899 Strange fuction(二分三分/爬山算法/模拟退火)

传送门


题目大意

现在有方程 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)=6x7+8x6+7x3+5x2kx(0x100),每次给出 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值