愤怒的小鸟

题目链接:https://www.luogu.org/problemnew/show/P2831

感觉这道题很不错,无论是练习爆搜还是状压,都无疑是最佳选择。

先说爆搜:

我们把猪从1-n来考虑,显然,对于每一只猪一共有三种选择:首先是在以前的抛物线被击中,若不满足,就是剩下的两种情况,一种是与其它没被抛物线击中的猪(即单独的猪)共同组成新的抛物线,另一种是自己单着,即自己组成一个抛物线。那么,最后的答案就是所有单独的猪加上抛物线的数量。详细见代码。

code:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;

const int N = 20, INF = 0x3f3f3f3f;
const double STD = 1e-8;
struct node{
	double x, y;
} p[N];
struct track {
	double a, b;
} t[N];
struct single {
	double x, y;;
} s[N];
int T, n, m, ans;

//浮点数的精度问题
bool judge(double a, double b) {
	return fabs(a-b)<STD;
}

void dfs(int now, int already, int single) {
	int i, j;
	bool flag = false;
	if (already+single>=ans) return ;//最优性剪枝
	if (now>n) {
		ans = already+single;
		return ;
	}
	for (i=1; i<=already; i++)//加入以前的抛物线
		if (judge(p[now].y, p[now].x*p[now].x*t[i].a+p[now].x*t[i].b)) {
			dfs(now+1, already, single);
			flag = true;
			break;
		}
	if (!flag) {//组成新的抛物线
		for (i=1; i<=single; i++) {
			if (judge(p[now].x,s[i].x)) continue;
			double x1 = p[now].x, x2 = s[i].x, y1 = p[now].y, y2 = s[i].y;
			double a = (x1*y2-x2*y1)/(x1*x2*x2-x1*x1*x2), b = (y1-a*x1*x1)/x1;
			if (a<0) {
				t[already+1].a = a, t[already+1].b = b;
				double tempx = s[i].x, tempy = s[i].y;
				for (j=i; j<single; j++)
					s[j].x = s[j+1].x, s[j].y = s[j+1].y;
				dfs(now+1, already+1, single-1);
				for (j=single; j>i; j--)//回溯
					s[j].x = s[j-1].x, s[j].y = s[j-1].y;
				s[i].x = tempx, s[i].y = tempy;
			}
		}//自己单着
		s[single+1].x = p[now].x, s[single+1].y = p[now].y;
		dfs(now+1, already, single+1);
	}
	return ;
}

int main() {
	cin >> T;
	while (T--) {
		memset(p, 0, sizeof(p));//别忘记每次初始化
		memset(t, 0, sizeof(t));
		memset(s, 0, sizeof(t));
		ans = INF;
		int i;
		cin >> n >> m;
		for (i=1; i<=n; i++)
			cin >> p[i].x >> p[i].y;
		dfs(1, 0, 0);
		cout << ans << endl;
	}
} 

然后是状压:

此题的数据范围很容易让人想到状压,思路如下:

首先一个很显然的思路就是我们可以用一个二进制数来表示猪的状态,即1表示相应位置的猪已被击中,0反之。在此基础之上我们可以用一个结构体来存储每个猪的坐标和状态,然后再处理一个stateij数组,表示i和j所组成的抛物线击中猪的情况(两个猪可以决定一个抛物线),详细的处理就是遍历一遍所有猪,看下i和j对应猪所组成的抛物线是否还能击中其它猪(要用到或运算)。最后,用一个dpi表示i所对应的状态最少需要几只鸟。转移的时候有点小技巧,我们可以枚举每个状态,然后枚举该状态没有的情况,不断的取最小值,详细看代码吧,感觉越说越不清楚了qwq。

code:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;

const int N = 20, INF = 0x3f3f3f3f;
const double STD = 1e-8;
struct pig {
    double x, y;
    int state;
} p[N];
int t, n, m, dp[1<<N], state[N][N];
double a, b;

//精度
bool judge(double a, double b) {
	return fabs(a-b)<STD;
}

//初始化很重要
void init() {
 	memset(dp, INF, sizeof(dp));
 	memset(state, 0, sizeof(state));
 	memset(p, 0, sizeof(p));
 	dp[0] = 0;
 	return ;
}

//获得ab的值
bool get_a_b(int i, int j) {
    double x1 = p[i].x, y1 = p[i].y, x2 = p[j].x, y2 = p[j].y;
    a = (y1*x2-y2*x1)/(x1*x2*(x1-x2));
    b = (y1-a*x1*x1)/x1;
    if (a<0) return true;
    else return false; 
}

void init_state() {
    int i, j, k;
    for (i = 0; i < n; i++) {
        for (j = i+1; j < n; j++) {
            if (judge(p[i].x, p[j].x)) continue;
            if (!get_a_b(i, j)) continue;//淘汰掉不合法的抛物线
            state[i][j] |= p[i].state, state[i][j] |= p[j].state;//基本状态
            for (k = 0; k < n; k++) {//看是否还能击中其它猪
            	if (k==i || k==j) continue;
            	if (judge(a*p[k].x*p[k].x+b*p[k].x, p[k].y)) state[i][j] |= p[k].state;
            }  
        }
    }
    return ;
}

int main() {
    scanf("%d", &t);
    while (t--) {
		init();
        int i, j, k;
        scanf("%d%d", &n, &m);
        for (i = 0; i < n; i++) {
            scanf("%lf%lf", &p[i].x, &p[i].y);
            p[i].state = (1<<i);//每个猪对应的状态,方便后面处理state
        }
        init_state();
        for (i = 0; i < (1<<n); i++) {//枚举所有状态
            for (j = 0; j < n; j++) {
                if (p[j].state&i) continue;//选出上述状态中没有的猪
                for (k = j; k < n; k++) {
                    if (k == j) dp[i|p[j].state] = min(dp[i|p[j].state], dp[i]+1);//最后可能会有一只猪单独占一条抛物线的情况
                    else dp[i|state[j][k]] = min(dp[i|state[j][k]], dp[i]+1);//抛物线的处理
                }
            }
        }
        printf("%d\n", dp[(1<<n)-1]);//输出最后的答案
    }
    return 0;
}
//注意各个量的初始化的值

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值