Loading

2025西安交大集训Day1:二分,三分,哈希,高精度,位运算,模拟退火

2025西安交大集训Day1:二分,三分,哈希,高精度,位运算,模拟退火

二分

详见2025dsfz集训Day2:二分与三分,三分在当前文章内已经重构过。

三分

三分算法详细解释

三分算法(Ternary Search)是一种用于在单峰函数(即函数在某一点左侧单调递增,右侧单调递减,或者相反)上寻找极值(最大值或最小值)的算法。它的基本思想是通过不断缩小搜索范围来逼近极值点。

算法步骤:

  1. 确定搜索范围:首先确定一个区间 \([left, right]\),确保极值点在这个区间内。
  2. 计算中间点:将区间分为三部分,计算两个中间点 \(mid1\)\(mid2\)
  • \(mid1 = left + (right - left) / 3\)
  • \(mid2 = right - (right - left) / 3\)
  1. 比较函数值:比较 \(f(mid1)\)\(f(mid2)\) 的大小:
  • 如果 \(f(mid1) < f(mid2)\),则极值点在 \([left, mid2]\) 区间内,更新 \(right = mid2\)
  • 如果 \(f(mid1) > f(mid2)\),则极值点在 \([mid1, right]\) 区间内,更新 \(left = mid1\)
  • 如果 \(f(mid1) == f(mid2)\),则极值点在 \([mid1, mid2]\) 区间内,更新 \(left = mid1\)\(right = mid2\)
  1. 重复步骤2和3:直到区间 \([left, right]\) 足够小,或者达到预定的精度要求。
  2. 返回结果:返回 \((left + right) / 2\) 作为极值点的近似值。

举例说明:

假设我们有一个凹函数 \(f(x) = (x - 3)^2\),我们希望在区间 \([0, 6]\) 上找到最小值。

  1. 初始区间 \([left, right] = [0, 6]\)
  2. 计算 \(mid1 = 0 + (6 - 0) / 3 = 2\)\(mid2 = 6 - (6 - 0) / 3 = 4\)
  3. 计算 \(f(mid1) = (2 - 3)^2 = 1\)\(f(mid2) = (4 - 3)^2 = 1\)
  4. 由于 \(f(mid1) == f(mid2)\),更新 \(left = 2\)\(right = 4\)
  5. 重复步骤2和3,直到区间足够小。

最终,我们会发现最小值出现在 \(x = 3\)

凹函数求最小值的模板代码

#include <iostream>
#include <cmath>
#include <iomanip>

using namespace std;

// 定义凹函数 f(x) = (x - 3)^2
double f(double x) {
	return (x - 3) * (x - 3);
}

// 三分算法求解凹函数最小值
double ternary_search(double left, double right, double eps) {
	while (right - left > eps) {
		double mid1 = left + (right - left) / 3;
		double mid2 = right - (right - left) / 3;

		if (f(mid1) < f(mid2)) {
			right = mid2;
		} else {
			left = mid1;
		}
	}
	return (left + right) / 2;
}

int main() {
	double left = 0.0;
	double right = 6.0;
	double eps = 1e-6; // 精度要求

	double result = ternary_search(left, right, eps);

	cout << "最小值出现在 x = " << fixed << setprecision(6) << result << endl;
	cout << "最小值为 f(x) = " << fixed << setprecision(6) << f(result) << endl;

	return 0;
}

代码解释:

  1. 函数 \(f(x)\):定义了我们要最小化的凹函数 \(f(x) = (x - 3)^2\)
  2. \(ternary_search\) 函数:实现了三分算法,通过不断缩小搜索范围来逼近最小值点。
  3. \(main\) 函数:设置初始搜索区间 \([0, 6]\) 和精度要求 \(eps = 1e-6\),调用 \(ternary_search\) 函数并输出结果。

输出结果:

最小值出现在 x = 3.000000
最小值为 f(x) = 0.000000

这个结果与我们的预期一致,最小值出现在 \(x = 3\),最小值为 \(0\)

哈希

详见我的字符串哈希学习笔记

位运算

没啥好说的。

模拟退火

  • 玄学算法会不了一点

习题

T1.P1883 【模板】三分 | 函数

  • 多个2次函数在同一个平面直角坐标系上的图像的最上面的值可以抽象成一个坡度。直接三分找最小值即可。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int t, n, a[N], b[N], c[N];
double f(double x, int i) {
	return x * x * a[i] + x * b[i] + c[i];
}
double check(double x) {
	double ans = f(x, 1);
	int i, j, k;
	for (i = 2; i <= n; i++)
		ans = max(ans, f(x, i));
	return ans;
}
void work( ) {
	cin >> n;
	int i, j, k;
	for (i = 1; i <= n; i++)cin >> a[i] >> b[i] >> c[i];
	double l = 0, r = 1000, emp = 1e-11, mid1, mid2;
	while (r - l > emp) {
		mid1 = l + (r - l) / 3.0;
		mid2 = r - (r - l) / 3.0;
		if (check(mid1) > check(mid2))l = mid1;
		else r = mid2;
	}
	printf("%.4lf\n", check(l));
}
int main( ) {
	int t;
	cin >> t;
	while (t--) work();
}

T2.P1601 A+B Problem(高精)

  • 朴素高精度

#include<bits/stdc++.h>
using namespace std;
int a[1000001],b[1000001],c[1000001],j;
bool x=false;
char s[1000001],ss[1000001];
int main() {

	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	memset(c,0,sizeof(c));
	scanf("%s%s",s,ss);
	a[0]=strlen(s);
	b[0]=strlen(ss);
	for(int i=1; i<=a[0]; i++) a[i]=s[a[0]-i]-'0';
	for(int i=1; i<=b[0]; i++) b[i]=ss[b[0]-i]-'0';
	for(j=1; j<=max(a[0],b[0])+1; j++) {
		c[j]=a[j]+b[j];
		if(c[j]>=10) {
			c[j]%=10;
			a[j+1]++;
		}
	}
	c[0]=j;
	if(c[j+1]>0) c[0]++;
	for(int i=c[0]; i>=1; i--) {
		if(x==false&&c[i]==0) continue;
		x=true;
		cout<<c[i];
	}
	if(x==false) cout<<0;
	printf("\n");
	return 0;
}

T3.P3370 【模板】字符串哈希

  • 哈希map板子题
//哈希做法
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int t, n, a[N], b[N], c[N];
double f(double x, int i) {
	return x * x * a[i] + x * b[i] + c[i];
}
double check(double x) {
	double ans = f(x, 1);
	int i, j, k;
	for (i = 2; i <= n; i++)
		ans = max(ans, f(x, i));
	return ans;
}
void work( ) {
	cin >> n;
	int i, j, k;
	for (i = 1; i <= n; i++)cin >> a[i] >> b[i] >> c[i];
	double l = 0, r = 1000, emp = 1e-11, mid1, mid2;
	while (r - l > emp) {
		mid1 = l + (r - l) / 3.0;
		mid2 = r - (r - l) / 3.0;
		if (check(mid1) > check(mid2))l = mid1;
		else r = mid2;
	}
	printf("%.4lf\n", check(l));
}
int main( ) {
	int t;
	cin >> t;
	while (t--) work();
}
//map做法,代码少得离谱(bushi
#include <bits/stdc++.h>
using namespace std;
map <string,int> mp;
int n;
int main(){
	cin>>n;
	string s;
	for(int i = 1;i <= n;i++){
		cin>>s;
		mp[s]++;
	}
	cout<<mp.size()<<endl;
}

T4.平衡点 / 吊打XXX

  • 模拟退火。我也不会
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <iostream>

constexpr int N = 10005;
int n, x[N], y[N], w[N];
double ansx, ansy, dis;

double Rand() { return (double)rand() / RAND_MAX; }

double calc(double xx, double yy) {
  double res = 0;
  for (int i = 1; i <= n; ++i) {
    double dx = x[i] - xx, dy = y[i] - yy;
    res += sqrt(dx * dx + dy * dy) * w[i];
  }
  if (res < dis) dis = res, ansx = xx, ansy = yy;
  return res;
}

void simulateAnneal() {
  double t = 100000;
  double nowx = ansx, nowy = ansy;
  while (t > 0.001) {
    double nxtx = nowx + t * (Rand() * 2 - 1);
    double nxty = nowy + t * (Rand() * 2 - 1);
    double delta = calc(nxtx, nxty) - calc(nowx, nowy);
    if (exp(-delta / t) > Rand()) nowx = nxtx, nowy = nxty;
    t *= 0.9999;
  }
  for (int i = 1; i <= 1000; ++i) {
    double nxtx = ansx + t * (Rand() * 2 - 1);
    double nxty = ansy + t * (Rand() * 2 - 1);
    calc(nxtx, nxty);
  }
}

int main() {
  std::cin.tie(nullptr)->sync_with_stdio(false);
  srand(1145);  // 注意,在实际使用中,不应使用固定的随机种子。
  std::cin >> n;
  for (int i = 1; i <= n; ++i) {
    std::cin >> x[i] >> y[i] >> w[i];
    ansx += x[i], ansy += y[i];
  }
  ansx /= n, ansy /= n, dis = calc(ansx, ansy);
  simulateAnneal();
  std::cout << std::fixed << std::setprecision(3) << ansx << ' ' << ansy
            << '\n';
  return 0;
}
posted @ 2025-03-22 10:48  FrankWkd  阅读(39)  评论(0)    收藏  举报