P2831 愤怒的小鸟 题解

CSDN同步

原题链接

简要题意:

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

前记

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

算法一

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

实际上,直接爆搜即可。

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

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

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

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

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

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

Link 代码

暴力最强大!

算法二

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

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

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

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

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

状态压缩 ( ext{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)).(不过 ( ext{AThousandSuns}) 提出了一种 (O(2^n cdot n)) 的算法,详见 AThousandSuns 的洛谷博客

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

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

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

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

#pragma GCC optimize(2)
#include<bits/stdc++.h>
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<<n)-1;k++) {
			for(int i=1;i<=cnt;i++) f[k|s[i]]=min(f[k|s[i]],f[k]+1);
			for(int i=1;i<=n;i++) f[k|(1<<(i-1))]=min(f[k|(1<<(i-1))],f[k]+1);
		} printf("%d
",f[(1<<n)-1]);
	} 
	return 0;
}


原文地址:https://www.cnblogs.com/bifanwen/p/13089466.html