1. 程式人生 > 實用技巧 >NOIP 2016 憤怒的小鳥

NOIP 2016 憤怒的小鳥

NOIP 2016 憤怒的小鳥

洛谷傳送門

JDOJ傳送門

Description

Kiana最近沉迷於一款神奇的遊戲無法自拔。

簡單來說,這款遊戲是在一個平面上進行的。

有一架彈弓位於(0,0)處,每次Kiana可以用它向第一象限發射一隻紅色的小鳥,小鳥們的飛行軌跡均為形如y=ax2+bx的曲線,其中a,b是Kiana指定的引數,且必須滿足a<0。

當小鳥落回地面(即x軸)時,它就會瞬間消失。

在遊戲的某個關卡里,平面的第一象限中有n只綠色的小豬,其中第i只小豬所在的座標為(xi,yi)。

如果某隻小鳥的飛行軌跡經過了(xi,yi),那麼第i只小豬就會被消滅掉,同時小鳥將會沿著原先的軌跡繼續飛行;

如果一隻小鳥的飛行軌跡沒有經過(xi,yi),那麼這隻小鳥飛行的全過程就不會對第i只小豬產生任何影響。

例如,若兩隻小豬分別位於(1,3)和(3,3),Kiana可以選擇發射一隻飛行軌跡為y=-x2+4x的小鳥,這樣兩隻小豬就會被這隻小鳥一起消滅。

而這個遊戲的目的,就是通過發射小鳥消滅所有的小豬。

這款神奇遊戲的每個關卡對Kiana來說都很難,所以Kiana還輸入了一些神祕的指令,使得自己能更輕鬆地完成這個遊戲。這些指令將在【輸入格式】中詳述。

假設這款遊戲一共有T個關卡,現在Kiana想知道,對於每一個關卡,至少需要發射多少隻小鳥才能消滅所有的小豬。由於她不會算,所以希望由你告訴她。

Input

第一行包含一個正整數T,表示遊戲的關卡總數。

下面依次輸入這T個關卡的資訊。每個關卡第一行包含兩個非負整數n,m,分別表示該關卡中的小豬數量和Kiana輸入的神祕指令型別。接下來的n行中,第i行包含兩個正實數(xi,yi),表示第i只小豬座標為(xi,yi)。資料保證同一個關卡中不存在兩隻座標完全相同的小豬。

如果m=0,表示Kiana輸入了一個沒有任何作用的指令。

如果m=1,則這個關卡將會滿足:至多用⌈n/3+1⌉只小鳥即可消滅所有小豬。

如果m=2,則這個關卡將會滿足:一定存在一種最優解,其中有一隻小鳥消滅了至少⌊n/3⌋只小豬。

保證1<=n<=18,0<=m<=2,0<xi,yi<10,輸入中的實數均保留到小數點後兩位。

上文中,符號⌈c⌉和⌊c⌋分別表示對c向上取整和向下取整

Output

對每個關卡依次輸出一行答案。

輸出的每一行包含一個正整數,表示相應的關卡中,消滅所有小豬最少需要的小鳥數量

Sample Input

2 2 0 1.00 3.00 3.00 3.00 5 2 1.00 5.00 2.00 8.00 3.00 9.00 4.00 8.00 5.00 5.00

Sample Output

1 1

HINT

【樣例解釋】
這組資料中一共有兩個關卡。
第一個關卡與【問題描述】中的情形相同,2只小豬分別位於(1.00,3.00)和 (3.00,3.00),只需發射一隻飛行軌跡為y = -x2 + 4x的小鳥即可消滅它們。
第二個關卡中有5只小豬,但經過觀察我們可以發現它們的座標都在拋物線 y = -x2 + 6x上,故Kiana只需要發射一隻小鳥即可消滅所有小豬。

【子任務】


題解:

n很小,求最優化,考慮狀壓DP。

上來很容易設狀態:\(dp[sta]\)表示殺豬狀態為sta的時候的最小价值,初值dp[0]=0,其餘為正無窮。

然後我們考慮轉移。

很容易想到的是轉移只跟拋物線的狀態有關。即一個拋物線能殺越來越多頭豬。那麼我們可以考慮預處理出每條拋物線都過哪些豬,然後根據這個轉移即可。

細節超級多,浮點數運算要人狗命。

拋物線最多的情況是任意兩點都構一條合法拋物線,當然a<0,然後萬一有構不了的,還要考慮每一點單獨拎一條拋物線。

所以一共最多也就是\(n^2+n\)條拋物線。也沒有很多,需要去重。

嗯嗯大致思路是這樣

程式碼:

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-8;
int t,n,m,lines[20][20],start[1<<20],dp[1<<20];
double x[20],y[20];
void equation(double &x,double &y,double a1,double b1,double c1,double a2,double b2,double c2)
{
	y=(a1*c2-a2*c1)/(a1*b2-a2*b1);
	x=(c1-b1*y)/a1;
}
int main()
{
	for(int i=0;i<(1<<18);i++)
    {
		int j=1;
		for(;j<=18 && i&(1<<(j-1));j++);
		    start[i]=j;
	}
	scanf("%d",&t);
	while(t--)
    {
		memset(lines,0,sizeof(lines));
		memset(dp,0x3f,sizeof(dp));
		dp[0]=0;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++) 
            scanf("%lf%lf",x+i,y+i);
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
            {
				if(fabs(x[i]-x[j])<eps) 
                    continue;
				double a,b;
				equation(a,b,x[i]*x[i],x[i],y[i],x[j]*x[j],x[j],y[j]);
				if(a>-eps) 
                    continue;
				for(int k=1;k<=n;k++)
					if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<eps) 
                        lines[i][j]|=(1<<(k-1));
			}
		for(int i=0;i<(1<<n);i++)
        {
			int j=start[i];
			dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+1);
			for(int k=1;k<=n;k++) 
                dp[i|lines[j][k]]=min(dp[i|lines[j][k]],dp[i]+1);
		}
		printf("%d\n",dp[(1<<n)-1]);
	}
    return 0;
}