• 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;	
    }
    
  • 相关阅读:
    关于登录或授权页面的错误提示
    弱网环境模拟工具
    Android Fragment 深度解析
    排序算法(七)
    排序算法(六)
    排序算法(五)
    java之数组
    排序算法(四)
    排序算法(三)
    排序算法(二)
  • 原文地址:https://www.cnblogs.com/lihan123/p/11658823.html
Copyright © 2020-2023  润新知