P2831 憤怒的小鳥 題解

博客園同步

原題鏈接

簡要題意:

平面直角座標系的第一象限有若干綠豬,小鳥要通過若干條函數解析線來消滅它們。每個小鳥可以把所有 y=ax2+bx(a<0)y=ax^2 + bx (a<0) 上的 (x,y)(x,y) 的所有綠豬消滅,當然沒有綠豬就是白走。問最少多少次後可以消滅所有的綠豬。TT 組數據。

前記

實際上,之前寫過一篇 騙分導論,裏面有點口出狂言,既然自己說了能 60pts60pts 那還不來填坑?

算法一

對於 70%70 \% 的數據,n12n \leq 12.

實際上,直接爆搜即可。

怎麼搜?對於每個搜索狀態,你可以用一個 vector\text{vector} 來記錄當前 剩下綠豬的座標,並記錄當前步數。

由於 vector\text{vector} 可以方便的刪除元素,我們枚舉當前 vector\text{vector} 的兩個座標並計算出 a,ba,b 的值,然後判斷 a<0a<0,合法則將所有 y=ax2+bxy=ax^2+bx 上的綠豬刪除,進入下一層。

這樣的話,如果你沒有開 long double\text{long double},可以得到 55pts55pts.

開了之後就是 70pts70pts. 臉黑啊

時間複雜度:O(70pts)O(\text{70pts}).

期望得分:60pts60pts. 實際得分:70pts70pts.

Link 代碼

暴力最強大!

算法二

對於 100%100 \% 的數據,n18n \leq 18T5T \leq 5.

顯然,我們會發現在上面的搜索中,冗餘的狀態太多了。

比方說你先把 1,41,4 一起打下來,再把 2,32,3 一起打下來,和把打它們的順序換一下,本質有什麼區別呢?——沒有,但是搜索中會大量出現類似 重複搜索同一個狀態 的現象,就會想斐波那契數列那樣越滾越大,最終 TLE\text{TLE}.

就上面一點而言,假設造個數據,保證每兩頭綠豬都無法一起打下,即每次只能幹掉一個,那這個程序的時間複雜度 \cdots \cdots

所以我們想解決這個問題,但是你認爲如果用 map<vector<pair<db,db> > >\texttt{map<vector<pair<db,db> > >} 實在是不好,還多一個 log\log,就和正解陰差陽錯了。所以我們想到,記憶化搜索的本質不就是 dp\text{dp}?而枚舉狀態的 dp\text{dp} 不就是 \cdots \cdots

狀態壓縮 dp\text{dp},簡稱狀壓!

我們可以用一個二進制數來表示當前狀態(顯然只有打下和沒打下兩個情況),然後先把所有解析線(至少能打下 22 頭綠豬的)統計在一起,然後枚舉狀態轉移即可。但這個說法不太清晰,需要建立理論模型。

ff 存儲狀態,f0f_0 ~ f2n1f_{{2^n}-1} 即爲所有狀態。把當前解析線能消滅的豬也有一個二進制數存在 ss 裏,每次枚舉狀態的時候,對於每個 ss 都要考慮一個轉移:

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

即如果當前線包含 ii 則直接用線解決;否則 +1+1.

但是你發現會有一些綠豬,只能單獨被打,不能和別的豬一起被打。

對於這樣的豬,我們需要再枚舉所有的豬,將它更新一遍。

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

顯然對於第 kk 個狀態,當前所有豬被打的答案可以直接更新掉,解決了這個問題。

最後f2n1f_{{2^n}-1} 就是答案。

時間複雜度:O(2nn2)O(2^n \cdot n^2).(不過 AThousandSuns\text{AThousandSuns} 提出了一種 O(2nn)O(2^n \cdot n) 的算法,詳見 AThousandSuns 的洛谷博客

期望得分:85pts85pts. 實際得分:100pts100pts.

理論上跑滿了約爲 8.4×1078.4 \times 10^7,挺危險的,擦邊球過去了。

這裏你就會明白爲什麼記憶化搜索過不了,因爲,記憶化用 map\text{map} 的時間複雜度是 O(2nn2logn)O(2^n \cdot n^2 \log n),你就多了 44 倍的常數,成功到了 2.7×1082.7 \times 10^8 一個更危險的級別。當然常數好卡過去我也不說什麼了

注意一下:計算解析式的時候可能存在一定精度問題,因此保留 10610^{-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\n",f[(1<<n)-1]);
	} 
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章