1. 程式人生 > >【狀壓DP】【NOIP提高組】憤怒的小鳥

【狀壓DP】【NOIP提高組】憤怒的小鳥

這是道不算水的狀壓DP

這道題對我的吸引力很大,為什麼呢,因為它的背景是遊戲啊

題目描述

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

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

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

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

在遊戲的某個關卡里,平面的第一象限中有 nn 只綠色的小豬,其中第 ii 只小豬所在的座標為 \left(x_i,y_i \right)(x 
i
​     ,y 
i
​     )。

如果某隻小鳥的飛行軌跡經過了 \left( x_i, y_i \right)(x 
i
​     ,y 
i
​     ),那麼第 ii 只小豬就會被消滅掉,同時小鳥將會沿著原先的軌跡繼續飛行;

如果一隻小鳥的飛行軌跡沒有經過 \left( x_i, y_i \right)(x 
i
​     ,y 
i
​     ),那麼這隻小鳥飛行的全過程就不會對第 ii 只小豬產生任何影響。

例如,若兩隻小豬分別位於 (
1,3)(1,3) 和 (3,3)(3,3),Kiana 可以選擇發射一隻飛行軌跡為 y=-x^2+4xy=−x 2 +4x 的小鳥,這樣兩隻小豬就會被這隻小鳥一起消滅。 而這個遊戲的目的,就是通過發射小鳥消滅所有的小豬。 這款神奇遊戲的每個關卡對 Kiana來說都很難,所以Kiana還輸入了一些神祕的指令,使得自己能更輕鬆地完成這個遊戲。這些指令將在【輸入格式】中詳述。 假設這款遊戲一共有 TT 個關卡,現在 Kiana想知道,對於每一個關卡,至少需要發射多少隻小鳥才能消滅所有的小豬。由於她不會算,所以希望由你告訴她。 輸入輸出格式 輸入格式: 第一行包含一個正整數 TT,表示遊戲的關卡總數。 下面依次輸入這 TT 個關卡的資訊。每個關卡第一行包含兩個非負整數 n,mn,m,分別表示該關卡中的小豬數量和 Kiana 輸入的神祕指令型別。接下來的 nn 行中,第 ii 行包含兩個正實數 x_i,y_ix i ​ ,y i ​ ,表示第 ii 只小豬座標為 (x_i,y_i)(x i ​ ,y i ​ )。資料保證同一個關卡中不存在兩隻座標完全相同的小豬。 如果 m
=0m=0,表示Kiana輸入了一個沒有任何作用的指令。 如果 m=1m=1,則這個關卡將會滿足:至多用 \lceil n/3 + 1 \rceil⌈n/3+1⌉ 只小鳥即可消滅所有小豬。 如果 m=2m=2,則這個關卡將會滿足:一定存在一種最優解,其中有一隻小鳥消滅了至少 \lfloor n/3 \rfloor⌊n/3⌋ 只小豬。 保證 1\leq n \leq 181≤n≤180\leq m \leq 20≤m≤20 < x_i,y_i < 100<x i ​ ,y i ​ <10,輸入中的實數均保留到小數點後兩位。 上文中,符號 \lceil c \rceil⌈c⌉ 和 \lfloor c \rfloor⌊c⌋ 分別表示對 cc 向上取整和向下取整,例如:\lceil
2.1 \rceil = \lceil 2.9 \rceil = \lceil 3.0 \rceil = \lfloor 3.0 \rfloor = \lfloor 3.1 \rfloor = \lfloor 3.9 \rfloor = 32.1⌉=⌈2.9⌉=⌈3.0⌉=⌊3.0⌋=⌊3.1⌋=⌊3.9⌋=3。 輸出格式: 對每個關卡依次輸出一行答案。 輸出的每一行包含一個正整數,表示相應的關卡中,消滅所有小豬最少需要的小鳥數量。 輸入輸出樣例 輸入樣例#1: 複製 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 輸出樣例#1: 複製 1 1
T

這道題可以寫狀壓,也可以搜尋(因為資料範圍小)

首先是思路

首先是關於拋物線,這個可以手推出公式(強行消元即可)

 1 pair<ld,ld> func(ld x1,ld y1,ld x2,ld y2) 2 {return make_pair((x2*y1-x1*y2)/(x1*x1*x2-x2*x2*x1),(x2*x2*y1-y2*x1*x1)/(x1*x2*x2-x2*x1*x1));} 

就是這樣

然後我們列舉每兩隻豬,看他們的拋物線是否a值<0(小鳥不能往上飛,從原點開始所以兩隻豬就能確定一個拋物線)

然後看這個拋物線是否存在,然後看這個拋物線還能否打到其他豬,也就是說,對於每個拋物線,都有一個壓成二進位制數儲存它能打到的豬的情況

然後dp即可,將拋物線與當前情況或(|)一下,進行更新,就是這樣

1 for(int i=0;i<(1<<(n));i++)
2             for(int j=1;j<=birdp;j++)
3                 dp[i|bird[j]]=min(dp[i]+1,dp[i|bird[j]]);

注意是1<<n還是1<<n+1,不細心就會有玄學錯誤,本蒟蒻卡了好久

最後注意細節,如果有的豬不在拋物線上,也就是需要單獨一隻小鳥打它,需要特判,就是從1<<n到0往下走,遇到第一個不是inf的就break,列舉0即可

還要注意一點就是bird陣列要清0,之前我只請了birdpp,以為是對的,但是因為bird每次都會在原來基礎上 | ,不清會只有10分(QAQ)

remember:long double型別讀入和輸出都是%Lf

上程式碼

 1 //><!!
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 typedef long double ld;typedef long long ll;using namespace std;
 6 int T,n,m,birdp;
 7 int dp[1<<20],bird[20*20];
 8 ld pigx[20],pigy[20],birda[20*20],birdb[20*20];
 9 ld aabs(ld a){return a<0?(-a):a;}
10 bool equal(ld a,ld b){return aabs(a-b)<=0.000000001?true:false;}
11 pair<ld,ld> func(ld x1,ld y1,ld x2,ld y2)
12 {return make_pair((x2*y1-x1*y2)/(x1*x1*x2-x2*x2*x1),(x2*x2*y1-y2*x1*x1)/(x1*x2*x2-x2*x1*x1));}
13 int main()
14 {
15     scanf("%d",&T);
16     for(int h=1;h<=T;h++)
17     {
18         memset(dp,0x3f,sizeof(dp)),birdp=0,dp[0]=0;memset(bird,0,sizeof(bird));
19         scanf("%d%d",&n,&m);
20         for(int i=1;i<=n;i++)
21             scanf("%Lf%Lf",&pigx[i],&pigy[i]);
22         for(int i=1;i<=n;i++)
23             for(int j=1;j<=n;j++)
24                 if(pigx[i]!=pigx[j])
25                 {
26                     pair<double,double> p=func(pigx[i],pigy[i],pigx[j],pigy[j]);
27                     if(p.first<0)
28                     {
29                         bool exist=0;
30                         for(int k=1;k<=birdp;k++)
31                             if(equal(birda[k],p.first) && equal(birdb[k],p.second))
32                                 {exist=1;break;}
33                         if(!exist)
34                         {    
35                             birda[++birdp]=p.first,birdb[birdp]=p.second;
36                             for(int k=1;k<=n;k++)
37                                 if(equal(p.first*pigx[k]*pigx[k]+p.second*pigx[k],pigy[k]))
38                                     bird[birdp]|=1<<(k-1);
39                         }
40                     }
41                 }
42         for(int i=0;i<(1<<(n));i++)
43             for(int j=1;j<=birdp;j++)
44                 dp[i|bird[j]]=min(dp[i]+1,dp[i|bird[j]]);
45         if(dp[(1<<(n))-1]!=0x3f3f3f3f)printf("%d\n",dp[(1<<(n))-1]);
46         else{
47             int ans=0,sit=0;
48             for(int i=(1<<(n))-1;i>=0;i--)
49                 if(dp[i]!=0x3f3f3f3f){ans=dp[i],sit=i;break;}
50             for(int i=1;i<=n;i++)
51                 if(!((1<<(i-1))&sit))ans++;
52             printf("%d\n",ans);
53         }
54     }
55     return 0;
56 }