浏览器标题切换
浏览器标题切换end
把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

LGOJP2831 愤怒的小鸟

题目链接

题目链接

题解

数据范围显然状压/爆搜。
考虑\(f[S]\)表示二进制下已打了的猪的集合。
可以枚举\(S\)的子集\(S_1\),判定\(S\)\(S_1\)的补集\(S_2\)是否合法。
判定可以通过待定系数法做到\(O(n)\)判定。若补集合法,则\(f[S]=\min\{f[S_1]+1 \}\)
复杂度是\(O(Tn3^n)\)。这样能\(70\)分。
考虑如何优化。因为\(n\)很小,所以可以\(O(n^3)\)预处理出\(g_{i,j}\)表示经过\(i,j\)两点的二次函数可达的点集。那么转移的时候枚举二次函数(通过枚举点\(i\)和点\(j\)),转移方程\(f[S|g_{i,j}]=\min\{f[S]+1\}\)
那么复杂度降到\(O(Tn^22^n)\)。已经可以通过本题了。
唐神的\(blog\)里面还有个优化。
一个结论:每个状态\(S\)用于转移的二次函数,一定经过该状态中不包含的第一个点\(x\)。因为最后的目标是选所有的点,这个点不选,在最后的最优方案中也一定会被其他点选到,而前面已经处理出了\(g_{i,j}\),可达点集已经都处理出来了。所以只用这个点\(x\)转移是合法的。
如果预处理出来每个状态\(S\)所对应的\(x\)的话复杂度就是\(O(Tn2^n)\)的。如果不处理的话会慢一点但也不会太多。

#include <bits/stdc++.h>
using namespace std;

const int N = 20;
const int inf = 0x3f3f3f3f;
const double eps = 1e-10;
int n, m, f[(1 << 18) + 5], T, g[N][N];
double a[N], b[N];

int main() {
	scanf("%d", &T);
	while(T--) {
		scanf("%d%d", &n, &m);
		for(int i = 0; i < n; ++i) scanf("%lf%lf", &a[i], &b[i]);
		memset(g, 0, sizeof(g)); memset(f, 0x3f, sizeof(f)); f[0] = 0; 
		for(int i = 0; i < n; ++i) {
			for(int j = i + 1; j < n; ++j) {
				if(a[i] == a[j]) continue;
				double A = (b[i] - b[j] * a[i] / a[j]) / (a[i] * a[i] - a[i] * a[j]);
				double B = (b[i] - A * a[i] * a[i]) / a[i];
				if(A >= 0) continue;
				for(int k = 0; k < n; ++k) 
					if(fabs(b[k] - (A * a[k] * a[k] + B * a[k])) <= eps) g[i][j] |= 1 << k;
			}
		}
		for(int S = 0; S < 1 << n; ++S) {
			for(int i = 0; i < n; ++i) {
				f[S | (1 << i)] = min(f[S | (1 << i)], f[S] + 1);
				if(!(S & (1 << i))) {
					for(int j = i + 1; j < n; ++j) 
						f[S | g[i][j]] = min(f[S | g[i][j]], f[S] + 1);
					break;
				}
			}
		}
		printf("%d\n", f[(1 << n) - 1]);
	}
}
posted @ 2019-08-09 12:26  henry_y  阅读(157)  评论(0编辑  收藏  举报