1. 程式人生 > >狀壓dp NOIP 2016 憤怒的小鳥

狀壓dp NOIP 2016 憤怒的小鳥

題意:有 n n 只豬,第 i i 只坐標為 ( x

i , y i ) (x_i,y_i) ,問最少要用多少形如 y
= a x 2 + b x y=ax^2+bx
的拋物線才能將所有豬打下來,要求這些拋物線都過 (
0 , 0 ) (0,0)
點,且 a < 0 a<0 ( n < = 18 ) (n<=18)

n的範圍如此小,我們考慮狀壓dp,對於每一對豬 i , j i,j ,我們可以用三個點 ( 0 , 0 ) , ( x i , y i ) , ( x j , y j ) (0,0),(x_i,y_i),(x_j,y_j) 確定一條拋物線,如果該拋物線的 a < 0 a<0 ,就把它留下來,並求出這條拋物線可以打下那些豬。我們將每隻豬是否被打下來的 01 01 狀態壓縮成十進位制數, d p [ i ] dp[i] 表示在狀態 i i 下最少需要多少條拋物線。我們列舉所有狀態,對於每一種狀態,我們列舉所有合法的拋物線,轉移方程為: d p [ i s [ j ] ] = m i n ( d p [ i s [ j ] ] , d p [ i ] + 1 ) ; s [ j ] j dp[i|s[j]]=min(dp[i|s[j]],dp[i]+1);,s[j]為第j條拋物線能打下的豬。
但是可能有些豬不能被已經求出的所有拋物線打下來,因為可能與這些豬形成的所有的拋物線 a a 都大於 0 0 ,所以我們還要列舉每一頭豬,看一頭一頭的打是否能更新答案。轉移方程為: d p [ i ( 1 < < ( j 1 ) ) ] = m i n ( d p [ i ( 1 < < ( j 1 ) ) ] , d p [ i ] + 1 ) ; dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+1);

要注意的地方:在判斷一條拋物線能否打下某一頭豬時,因為都是double型變數,所以要

if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<=eps)

以下做法可能會出鍋QAQ

if(a*x[k]*x[k]+b*x[k]-y[k]==0)

最後的結果就是 d p [ ( 1 &lt; &lt; n ) 1 ] dp[(1&lt;&lt;n)-1] 了。

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-6;
int T,n,m,dp[550000],s[550000],num;
double x[1000],y[1000];
int main()
{
    cin>>T;
    while(T--)
    {
        num=0;
        memset(x,0,sizeof(x));
        memset(y,0,sizeof(y));
        memset(s,0,sizeof(s));
        memset(dp,0x3f,sizeof(dp));
        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=i+1;j<=n;++j)
            {
                if(x[i]!=x[j])
                {
                    double a=((x[i]*y[j])-(x[j]*y[i]))/((x[i]*x[j]*x[j])-(x[i]*x[i]*x[j]));
                    double b=(y[i]/x[i])-(((x[i]*y[j])-(x[j]*y[i]))/((x[j]*x[j])-(x[i]*x[j])));
                    if(a<0)
                    {
                        num++;
                        for(int k=1;k<=n;++k)
                            if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<=eps)
                                s[num]|=(1<<(k-1));
                    }	
                }
            }
        dp[0]=0;
        for(int i=0;i<(1<<n);++i)
        {
            for(int j=1;j<=num;++j)
                dp[i|s[j]]=min(dp[i|s[j]],dp[i]+1);//按拋物線打 
            for(int j=1;j<=n;++j)
                dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+1);//只打一頭 
        }
        printf("%d\n",dp[(1<<n)-1]);
    }
    return 0;
}