洛谷 P2831 愤怒的小鸟

思路

未优化

状压 DP \text{DP} DP

n ≤ 18 n\leq 18 n18,不是暴搜就是状压,因为我 j i o jio jio得状压会比较好理解,所以就写一篇状压的题解叭

首先我们要预处理出经过任意两点的抛物线可以击中的小猪有哪些,可以用 l i n e [ i ] [ j ] line[i][j] line[i][j]来表示经过 i , j i,j i,j的抛物线经过的小猪的集合,集合用二进制数来表示

  • 这里有一个小问题就是如何求抛物线 y = a x 2 + b x y=ax^2+bx y=ax2+bx中的 a , b a,b a,b

    假设目前的抛物线经过 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2)两点,已知 x > 0 x>0 x>0,那么有

    y 1 = a x 1 2 + b x 1 y_1=ax_1^2+bx_1 y1=ax12+bx1

    y 2 = a x 2 2 + b x 2 y_2=ax_2^2+bx_2 y2=ax22+bx2

    a x 1 + b = y 1 x 1 ax_1+b=\frac{y_1}{x_1} ax1+b=x1y1

    a x 2 + b = y 2 x 2 ax_2+b=\frac{y_2}{x_2} ax2+b=x2y2

    两式做差得

    a ( x 1 − x 2 ) = y 1 x 1 − y 2 x 2 a(x_1-x_2)=\frac{y_1}{x_1} - \frac{y_2}{x_2} a(x1x2)=x1y1x2y2

    所以

    a = y 1 x 1 − y 2 x 2 ( x 1 − x 2 ) a=\frac{\frac{y_1}{x_1} - \frac{y_2}{x_2}}{(x_1-x_2)} a=(x1x2)x1y1x2y2

    b = y 1 x 1 − a ∗ x 1 b=\frac{y_1}{x_1}-a*x_1 b=x1y1ax1

处理完之后就要想一想如何 DP \text{DP} DP

我们设 d p [ s ] dp[s] dp[s]表示消灭集合 s s s内所有小猪所用的最少的小鸟数

显然 d p [ 0 ] = 0 dp[0]=0 dp[0]=0,因为没有猪当然用不到鸟

假设当前的状态为 s s s,抛物线为经过 i , j i,j i,j点的抛物线,这条抛物线打掉的小猪的状态为 l i n e [ i ] [ j ] line[i][j] line[i][j],那么有

d p [ s ∣ l i n e [ i ] [ j ] ] = min ⁡ ( d p [ s ∣ l i n e [ i ] [ j ] ] , d p [ s ] + 1 ) dp[s|line[i][j]] = \min(dp[s|line[i][j]],dp[s] + 1) dp[sline[i][j]]=min(dp[sline[i][j]],dp[s]+1)

其中 s ∣ l i n e [ i ] [ j ] s|line[i][j] sline[i][j]表示当前状态 s s s在增加了经过 i , j i,j i,j点的这条抛物线之后能打到的小猪的集合,显然要从 d p [ s ∣ l i n e [ i ] [ j ] ] dp[s|line[i][j]] dp[sline[i][j]] d p [ s ] + 1 dp[s]+1 dp[s]+1中取最小

时间复杂度 O ( T n 2 2 n ) O(Tn^22^n) O(Tn22n)O(能过才怪),在洛谷上吸氧( O 2 O2 O2)可以过

优化

随意选择 s s s内的一只小猪 j j j,那么 j j j最后一定会被一只小鸟消灭,所以我们固定住这只小猪 j j j,只枚举 k k k转移

更详细见AThousandSuns的题解

时间复杂度 O ( T n 2 n ) O(Tn2^n) O(Tn2n),稳了

代码

未优化

/*
Author:loceaner
*/
#include 
#include 
#include 
#include 
#include 
#define eps (1e-6)
using namespace std;

const int A = 5e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar(); int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

int n, m, num, line[20][20], dp[B];
struct Pig { double x, y; } p[A]; //猪猪结构体 

bool cmp(Pig a, Pig b) { //根据坐标排序猪猪 
	return (a.x != b.x) ? (a.x < b.x) : (a.y < b.y);
} 

struct Line { //抛物线结构体,包含ax^2+bx中的a和b 
	double a, b;
	bool judge(Pig qwq) { //判断一个点是否在这个抛物线上 
		double val = a * qwq.x * qwq.x + b * qwq.x, Y = qwq.y;
		return ((val - Y) >= -eps && (val - Y) <= eps); 
	}
};

inline Line get(Pig a, Pig b) { //已知两点坐标,求这两点所处的抛物线
	//初中知识,推导见思路 
	Line ans;
	ans.a = (a.y / a.x - b.y / b.x) / (a.x - b.x);
	ans.b = (a.y / a.x - ans.a * a.x);
	return ans;	
}

int main() {
	int T = read();
	while (T--) {
		n = read(), m = read();
		memset(line, 0, sizeof(line)); //多测不清空,爆零两行泪 
		memset(dp, 0x3f, sizeof(dp));
		for (int i = 1; i <= n; i++) scanf("%lf%lf", &p[i].x, &p[i].y);
		sort(p + 1, p + 1 + n, cmp); //排序猪猪 
		for (int i = 1; i <= n; i++) line[i][i] = 1 << (i - 1); 
		//处理只经过一个点的抛物线,不加就会人没了…… 
		for (int i = 1; i <= n; i++) //预处理出所有抛物线 
			for (int j = 1; j < i; j++) {
				Line now = get(p[i], p[j]);
				if (now.a >= 0) continue;
				for (int k = 1; k <= n; k++) //line[i][j]抛物线能经过的猪猪集合 
					if (now.judge(p[k])) line[i][j] |= 1 << (k - 1);
				line[j][i] = line[i][j];
			}
		int U = (1 << n) - 1; //全集 
		dp[0] = 0; //没猪当然是0 
		for (int i = 1; i <= n; i++) //快乐地DP吧! 
			for (int j = 1; j <= n; j++) 
				for (int k = 0; k <= U; k++) 
					dp[k | line[i][j]] = min(dp[k | line[i][j]], dp[k] + 1);
		cout << dp[U] << '\n';
	} 
	return 0;
}

优化

/*
Author:loceaner
*/
#include 
#include 
#include 
#include 
#include 
#define eps (1e-6)
using namespace std;

const int A = 5e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar(); int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

int n, m, num, line[20][20], dp[B], must[B];
struct Pig { double x, y; } p[A];

bool cmp(Pig a, Pig b) {
	return (a.x != b.x) ? (a.x < b.x) : (a.y < b.y);
} 

struct Line {
	double a, b;
	bool judge(Pig qwq) {
		double val = a * qwq.x * qwq.x + b * qwq.x, Y = qwq.y;
		return ((val - Y) >= -eps && (val - Y) <= eps); 
	}
};

inline Line get(Pig a, Pig b) {
	Line ans;
	ans.a = (a.y / a.x - b.y / b.x) / (a.x - b.x);
	ans.b = (a.y / a.x - ans.a * a.x);
	return ans;	
}

int main() {
	for (int i = 0; i < (1 << 18); i++) {
		int j = 1;
		for (; j <= 18 && i & (1 << (j - 1)); j++);
		must[i] = j;
	}
	int T = read();
	while (T--) {
		num = 0;
		n = read(), m = read();
		memset(line, 0, sizeof(line)); //多测不清空,爆零两行泪 
		memset(dp, 0x3f, sizeof(dp));
		for (int i = 1; i <= n; i++) scanf("%lf%lf", &p[i].x, &p[i].y);
		sort(p + 1, p + 1 + n, cmp);
//		for (int i = 1; i <= n; i++) line[i][i] = 1 << (i - 1);
		//因为下面枚举must[i]的时候就是枚举的这个,所以就不需要了 
		for (int i = 1; i <= n; i++) 
			for (int j = 1; j < i; j++) {
				Line now = get(p[i], p[j]);
				if (now.a >= 0) continue;
				for (int k = 1; k <= n; k++) 
					if (now.judge(p[k])) line[i][j] |= 1 << (k - 1);
				line[j][i] = line[i][j];
			}
		int U = (1 << n) - 1;
		dp[0] = 0;
		for (int i = 0; i <= U; i++) {
			int j = must[i];
			dp[i | (1 << (j - 1))] = min(dp[i | (1 << (j - 1))], dp[i] + 1); //单独处理 
			for (int k = 1; k <= n; k++) 
				dp[i | line[j][k]] = min(dp[i | line[j][k]], dp[i] + 1);
		}
		cout << dp[U] << '\n';
	} 
	return 0;
}

你可能感兴趣的:(洛谷)