「JSOI2016」炸弹攻击(模拟退火)

https://loj.ac/problem/2076

模拟退火居然不出题答,这个出题人脑子进水了。

由最小圆覆盖那一套,最优的圆是以上三种情况之一:
1.一个答案点就是圆,半径—>0
2.两个点连成的线段为直径的圆
3.三点共园

直接枚举,判断是\(O(m^4)\)的。
可能可以优化掉一个\(m\),然后你发现做不下去了。

考虑退火,这个题直接以覆盖的点数作为价值退火是不太行的。

当价值相同时,应取当前圆的最大半径作为第二关键字。

然后调整发现,应该使往劣的方向走的概率变小。

这么个连样例都吃力的做法它能过。

Code:

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

#define db double

const int N = 1005;

int n, m;
db R, r[N];

struct P {
	db x, y;
	P(db _x = 0, db _y = 0) {
		x = _x, y = _y;
	}
} a[N], b[N];

#define sqr(x) ((x) * (x))
db dis(P a, P b) {
	return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y));
}

ll rand(ll x, ll y) {
	return ((ll) RAND_MAX * rand() + rand()) % (y - x + 1) + x;
}

db rd() {
	return (db) rand(0, 1e8) / 1e8;
}

const db eps = 1e-8;

P calc(P c) {
	db mi = R;
	fo(i, 1, n) mi = min(mi, dis(a[i], c) - r[i]);
	int s = 0;
	fo(i, 1, m) s += dis(c, b[i]) <= mi;
	return P(s, mi);
}

int main() {
	srand(time(0) + clock());
	scanf("%d %d %lf", &n, &m, &R);
	fo(i, 1, n) scanf("%lf %lf %lf", &a[i].x, &a[i].y, &r[i]);
	fo(i, 1, m) scanf("%lf %lf", &b[i].x, &b[i].y);
	int ans = 0;
	fo(ii, 1, min(50000 / m, 1000)) {
		P c = b[rand(1, m)];
		for(db T = 100; T > eps; T *= 0.99) {
			P d = c;
			d.x += (rd() - 0.5) * T * (R / 200), d.y += (rd() - 0.5) * T * (R / 200);
			P v1 = calc(c), v2 = calc(d);
			ans = max(ans, (int) v2.x);
			if(v2.x > v1.x) {
				c = d;
				continue;
			}
			if(v1.x == v2.x) {
				if(v2.y > v1.y) {
					c = d;
					continue;
				}
				if(exp((db) (v2.y - v1.y) / T) / 10 * 1e9 > rand(1, 1e9)) {
					c = d;
				}
				continue;
			}
			if(exp((db) (v2.x - v1.x) / T) / 10 * 1e9 > rand(1, 1e9)) {
				c = d;
			}
		}
	}
	
	pp("%d\n", ans);
}
posted @ 2020-04-19 22:22  Cold_Chair  阅读(178)  评论(0编辑  收藏  举报