[洛谷P2831]愤怒的小鸟

280 篇文章 1 订阅
220 篇文章 2 订阅

题目

传送门 to luogu

思路

看到 n ≤ 18 n\le 18 n18,直接状态压缩。然而每次找两只猪,串出一个抛物线,然后检查打到谁,这就是 O ( n 3 2 n ) \mathcal O(n^32^n) O(n32n) 了……

有一个优化:反正编号最小的 “幸存猪” 迟早要死,你完全可以现在就要求把它干掉。——前提是 每次操作的代价与顺序无关

还有玄学的优化,就是类似快速枚举子集的方法。看代码吧。主要是这玩意儿太高性能了……

p . s . p.s. p.s. 二次函数基础不过关怎么办?拉格朗日插值!卡精度?两位小数,直接乘 100 100 100 呗!

代码

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;

const int MaxN = 18;

int n, m, x[MaxN], y[MaxN];
long long calculate(int i,int j,int x0){
	long long r = 1ll*x0*(x0-x[j])*x[j]*y[i]-1ll*(x0-x[i])*x0*x[i]*y[j];
	if(r%(1ll*(x[i]-x[j])*x[i]*x[j])) return -1;
	return r/(1ll*(x[i]-x[j])*x[i]*x[j]);
}

int dp[1<<MaxN], highbit[1<<MaxN];
int main(){
	for(int i=0; i<MaxN; ++i)
		highbit[1<<i] = i;
	for(int i=0; i<(1<<MaxN); ++i)
		if((i&-i) != i)
			highbit[i] = highbit[i-(i&-i)];
	int T; scanf("%d",&T);
	while(T --){
		scanf("%d %d",&n,&m);
		for(int i=0,a,b; i<n; ++i){
			scanf("%d.%d",&a,&b);
			x[i] = a*100+b;
			scanf("%d.%d",&a,&b);
			y[i] = a*100+b;
		}
		for(int i=1; i<n; ++i)
			for(int j=0; j<n-i; ++j)
				if(x[j] > x[j+1]){
					swap(x[j],x[j+1]);
					swap(y[j],y[j+1]);
				}
		for(int S=1; S<(1<<n); ++S)
			dp[S] = dp[S-(S&-S)]+1;
		for(int S=0,id; S<(1<<n)-1; ++S){
			int vanS = (~S)&((1<<n)-1);
			id = highbit[vanS&(-vanS)];
			for(int i=highbit[vanS]; i!=id; i=highbit[((1<<i)-1)&vanS])
				if(x[i] != x[id] and x[id]*y[i]-x[i]*y[id] < 0){
					int newS = S;
					for(int j=i; true; j=highbit[((1<<j)-1)&vanS]){
						if(calculate(id,i,x[j]) == y[j])
							newS |= (1<<j);
						if(not j) break;
					}
					dp[newS] = min(dp[newS],dp[S]+1);
				}
		}
		printf("%d\n",dp[(1<<n)-1]);
	}
	return 0;
}

后记

什么叫做 高性能 啊?
在这里插入图片描述

时限是 2 s 2\rm s 2s 。再看看另一位大佬的搜索做法:
在这里插入图片描述
与人的体质真是不能一概而论啊!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值