P2831 愤怒的小鸟 题解

CSDN同步

原题链接

简要题意:

平面直角坐标系的第一象限有若干绿猪,小鸟要通过若干条函数解析线来消灭它们。每个小鸟可以把所有 \(y=ax^2 + bx (a<0)\) 上的 \((x,y)\) 的所有绿猪消灭,当然没有绿猪就是白走。问最少多少次后可以消灭所有的绿猪。\(T\) 组数据。

前记

实际上,之前写过一篇 骗分导论,里面有点口出狂言,既然自己说了能 \(60pts\) 那还不来填坑?

算法一

对于 \(70 \%\) 的数据,\(n \leq 12\).

实际上,直接爆搜即可。

怎么搜?对于每个搜索状态,你可以用一个 \(\text{vector}\) 来记录当前 剩下绿猪的坐标,并记录当前步数。

由于 \(\text{vector}\) 可以方便的删除元素,我们枚举当前 \(\text{vector}\) 的两个坐标并计算出 \(a,b\) 的值,然后判断 \(a<0\),合法则将所有 \(y=ax^2+bx\) 上的绿猪删除,进入下一层。

这样的话,如果你没有开 \(\text{long double}\),可以得到 \(55pts\).

开了之后就是 \(70pts\). 脸黑啊

时间复杂度:\(O(\text{70pts})\).

期望得分:\(60pts\). 实际得分:\(70pts\).

Link 代码

暴力最强大!

算法二

对于 \(100 \%\) 的数据,\(n \leq 18\)\(T \leq 5\).

显然,我们会发现在上面的搜索中,冗余的状态太多了。

比方说你先把 \(1,4\) 一起打下来,再把 \(2,3\) 一起打下来,和把打它们的顺序换一下,本质有什么区别呢?——没有,但是搜索中会大量出现类似 重复搜索同一个状态 的现象,就会想斐波那契数列那样越滚越大,最终 \(\text{TLE}\).

就上面一点而言,假设造个数据,保证每两头绿猪都无法一起打下,即每次只能干掉一个,那这个程序的时间复杂度 \(\cdots \cdots\)

所以我们想解决这个问题,但是你认为如果用 \(\texttt{map > >}\) 实在是不好,还多一个 \(\log\),就和正解阴差阳错了。所以我们想到,记忆化搜索的本质不就是 \(\text{dp}\)?而枚举状态的 \(\text{dp}\) 不就是 \(\cdots \cdots\)

状态压缩 \(\text{dp}\),简称状压!

我们可以用一个二进制数来表示当前状态(显然只有打下和没打下两个情况),然后先把所有解析线(至少能打下 \(2\) 头绿猪的)统计在一起,然后枚举状态转移即可。但这个说法不太清晰,需要建立理论模型。

\(f\) 存储状态,\(f_0\) ~ \(f_{{2^n}-1}\) 即为所有状态。把当前解析线能消灭的猪也有一个二进制数存在 \(s\) 里,每次枚举状态的时候,对于每个 \(s\) 都要考虑一个转移:

f[k|s[i]]=min(f[k|s[i]],f[k]+1);

即如果当前线包含 \(i\) 则直接用线解决;否则 \(+1\).

但是你发现会有一些绿猪,只能单独被打,不能和别的猪一起被打。

对于这样的猪,我们需要再枚举所有的猪,将它更新一遍。

f[k|(1<<(i-1))]=min(f[k|(1<<(i-1))],f[k]+1);

显然对于第 \(k\) 个状态,当前所有猪被打的答案可以直接更新掉,解决了这个问题。

最后\(f_{{2^n}-1}\) 就是答案。

时间复杂度:\(O(2^n \cdot n^2)\).(不过 \(\text{AThousandSuns}\) 提出了一种 \(O(2^n \cdot n)\) 的算法,详见 AThousandSuns 的洛谷博客)

期望得分:\(85pts\). 实际得分:\(100pts\).

理论上跑满了约为 \(8.4 \times 10^7\),挺危险的,擦边球过去了。

这里你就会明白为什么记忆化搜索过不了,因为,记忆化用 \(\text{map}\) 的时间复杂度是 \(O(2^n \cdot n^2 \log n)\),你就多了 \(4\) 倍的常数,成功到了 \(2.7 \times 10^8\) 一个更危险的级别。当然常数好卡过去我也不说什么了

注意一下:计算解析式的时候可能存在一定精度问题,因此保留 \(10^{-6}\) 即可。

#pragma GCC optimize(2)
#include
using namespace std;

#define db long double
const int N=1e6+1; 

inline int read(){char ch=getchar(); int f=1; while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0; while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f;}

inline void write(int x) {
	if(x<0) {putchar('-');write(-x);return;}
	if(x<10) {putchar(char(x%10+'0'));return;}
	write(x/10);putchar(char(x%10+'0'));
}

int n,m,T,cnt=0;
int f[N],s[501];
db x[101],y[101];

int main() {
	T=read(); while(T--) {
		n=read(),m=read();
		memset(f,0x3f,sizeof(f));
		memset(s,0,sizeof(s));
		cnt=0; f[0]=0; //初始化
		for(int i=1;i<=n;i++) cin>>x[i]>>y[i];
		for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			if(x[i]-x[j]) {
				double a=(y[i]-y[j]*x[i]/x[j])/x[i]/(x[i]-x[j]);
				double b=(y[i]*x[j]*x[j]/x[i]/x[i]-y[j])/(x[j]*x[j]/x[i]-x[j]);
				if(a<0) { //计算合法的解析式
					cnt++;
					for(int k=1;k<=n;k++)
						if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<=1e-6) s[cnt]|=(1<<(k-1)); 
				} //记录解析式
			}
		for(int k=0;k<=(1<

你可能感兴趣的:(P2831 愤怒的小鸟 题解)