狀壓dp NOIP 2016 憤怒的小鳥
阿新 • • 發佈:2018-11-08
題意:有 只豬,第 只坐標為 ,問最少要用多少形如 的拋物線才能將所有豬打下來,要求這些拋物線都過 點,且 。
n的範圍如此小,我們考慮狀壓dp,對於每一對豬
,我們可以用三個點
確定一條拋物線,如果該拋物線的
,就把它留下來,並求出這條拋物線可以打下那些豬。我們將每隻豬是否被打下來的
狀態壓縮成十進位制數,
表示在狀態
下最少需要多少條拋物線。我們列舉所有狀態,對於每一種狀態,我們列舉所有合法的拋物線,轉移方程為:
但是可能有些豬不能被已經求出的所有拋物線打下來,因為可能與這些豬形成的所有的拋物線
都大於
,所以我們還要列舉每一頭豬,看一頭一頭的打是否能更新答案。轉移方程為:
要注意的地方:在判斷一條拋物線能否打下某一頭豬時,因為都是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)
最後的結果就是 了。
#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;
}