NOIP 2016愤怒的小鸟(状压DP)

60分做法:

暴力枚举两个没被覆盖点,形成一个合法的抛物线,在扫一遍能在抛物线上的点,打上标记即可。

#include<queue>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstring>
using namespace std;
int t;
int n,m,U,w;
double x[30],y[30];
int p[30];
int ans=0;
bool vis[30];
bool check(int id,double a,double b)
{
 if(fabs(a*x[id]*x[id]+b*x[id]-y[id])<=0.000001)
 return 1;
 return 0;	
}
void dfs(int cnt,int sum)
{
  if(sum>ans||w>U) return;
  if(cnt==n)
  {
   ans=min(ans,sum);
   return;	
  }
  for(int i=1;i<=n;i++)
  {
   if(vis[i]) continue;
   for(int j=i+1;j<=n;j++)
   {
   	 w++;
     if(vis[j]) continue;
     double a=(x[j]*y[i]-x[i]*y[j])/(x[i]*x[j]*(x[i]-x[j]));
     double b=(y[i]-a*x[i]*x[i])/x[i];
     if(a>=0) continue;
     vis[i]=vis[j]=1;
     int tot=0;
	 for(int k=j+1;k<=n;k++)//因为在前面能选的点,之前也可以枚举到,这种情况可以覆盖到现在这个情况,所以可以从j往后枚举,减少重复情况
	 {
	   if(vis[k]) continue;
	   if(check(k,a,b))
	   vis[k]=1,p[++tot]=k;
	 }
	 dfs(cnt+2+tot,sum+1);
	 for(int k=1;k<=tot;k++)
	 vis[p[k]]=0;
	 vis[i]=vis[j]=0;
   }
  }
  ans=min(n-cnt+sum,ans);
}
int main()
{
 scanf("%d",&t); U=20000000/t;//卡时能过75
 while(t--)
 {
  w=0;
  ans=1e9;
  memset(vis,0,sizeof(vis));
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++)
  scanf("%lf%lf",&x[i],&y[i]);
  dfs(0,0);
  printf("%d
",ans);
 }
 return 0;	
}

满分做法:

看到n<=18,可以想到状压DP。(dp[S])表示表示已经死了的猪的集合状态为S时最少要发射的鸟数(即抛物线)。

考虑转移:

(dp[0]=0)

(dp[S|line[i][j]]=min(dp[S|line[i][j]],dp[S]+1))

(dp[S|(1<<(i-1)]=min(dp[S|(1<<(i-1)],dp[S]+1))(单独打一个猪)

(line[i][j])表示第i个点和第j个点连成的抛物线可以打死猪的集合。

本题可以优化到(O(Tn2^n)).令(low[S])表示S状态第一个零的位置。这个位置是一定要打的,如果这一次转移不打,那以后还要再回过头来打,这就是多余的转移。所以由 S扩展的转移的所有线都要经过(low[S])。之后枚举抛物线即可。

#include<queue>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstring>
using namespace std;
const int maxm=1<<20;
const double eq=1e-8;
int t;
int n,m;
double x[30],y[30];
int low[maxm];//表示i状态的第一个0的位置 
int dp[maxm];//表示i状态的最少抛物线数 
int line[30][30];//表示经过i,j的抛物细经过所有点的状态 
bool check(int id,double a,double b)
{
 if(fabs(a*x[id]*x[id]+b*x[id]-y[id])<=eq)
 return 1;
 return 0;	
}
int main()
{
 for(int i=0;i<(1<<18);i++)
 {
   int j=1;
   for(;j<=18,i&(1<<(j-1));j++);
   low[i]=j;
 }
 scanf("%d",&t);
 while(t--)
 {
  memset(dp,63,sizeof(dp));
  memset(line,0,sizeof(line));
  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])<eq) continue;
  	  double a=(x[j]*y[i]-x[i]*y[j])/(x[i]*x[j]*(x[i]-x[j]));
      double b=(y[i]-a*x[i]*x[i])/x[i];
      if(a>-eq) continue;
      for(int k=1;k<=n;k++)
      {
       if(check(k,a,b))
	   line[i][j]|=(1<<(k-1));	
      }
  	}
  }
  dp[0]=0;
  for(int i=0;i<(1<<n);i++)
  {
    int j=low[i];//能过的点尽量早过 
    dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+1);
    for(int k=1;k<=n;k++)
	dp[i|line[j][k]]=min(dp[i|line[j][k]],dp[i]+1);
  }
  printf("%d
",dp[(1<<n)-1]);
 }
 return 0;	
}
原文地址:https://www.cnblogs.com/lihan123/p/11658823.html