【狀壓DP】【NOIP提高組】憤怒的小鳥
阿新 • • 發佈:2019-01-12
這是道不算水的狀壓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 只小豬產生任何影響。 例如,若兩隻小豬分別位於 (T1,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≤18,0\leq m \leq 20≤m≤2,0 < x_i,y_i < 100<x i ,y i <10,輸入中的實數均保留到小數點後兩位。 上文中,符號 \lceil c \rceil⌈c⌉ 和 \lfloor c \rfloor⌊c⌋ 分別表示對 cc 向上取整和向下取整,例如:\lceil2.1 \rceil = \lceil 2.9 \rceil = \lceil 3.0 \rceil = \lfloor 3.0 \rfloor = \lfloor 3.1 \rfloor = \lfloor 3.9 \rfloor = 3⌈2.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
這道題可以寫狀壓,也可以搜尋(因為資料範圍小)
首先是思路
首先是關於拋物線,這個可以手推出公式(強行消元即可)
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 }