[noip2016]愤怒的小鸟<状压dp+暴搜>

题目链接:https://vijos.org/p/2008

现在回过头去看去年的考试题,发现都不是太难,至少每道题都有头绪了。。。

这道题的数据范围是18,这么小,直接暴力呗,跑个暴搜就完了,时间也就O(n^3)

【思路】

先枚举任意两个的抛物线,这个位置需要O(n^2),接着针对每一个抛物线看可以经过多少点,暴力跑一个,时间复杂度O(n^3),不过这一步可以在枚举抛物线时做。。

接着是用一个数组mark[i][j]记录经过点i,j的抛物线可以穿过哪些点。。这个位置,我们就可以用状态压缩解决,一个二进制,第i位为1表示可以穿过这个点,0则不行

然后就是f数组,f[i]的i状态下需要多少个鸟去打,i的二进制下表示当前有哪些点被穿过了,然后i的范围在二进制下就是0到11111111(n个1)

然后就是最后一个大循环,首先判断能不能达到状态i,如果可以就枚举第j的点,如果没有被穿过就是f[i|state[j]]=min(f[i|state[j]],f[i]+1)

(要么是原来的状态,要么从当前状态发射一只鸟)

接着就是枚举从j穿过的抛物线类型,f[i|mark[j][k]]=min(f[i|mark[j][k]],f[i]+1)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<queue>
 5 #include<cmath>
 6 #include<algorithm>
 7 #include<cstdlib>
 8 #define maxn 30
 9 #define LL long long
10 using namespace std;
11 
12 double x[maxn],y[maxn];
13 double ex=1e-8;
14 int mark[maxn][maxn];
15 int f[(1<<18)+5],state[maxn];
16 int n,m;
17 
18 void work(){
19     scanf("%d%d",&n,&m);
20     for(int i=0;i<n;i++){
21         scanf("%lf%lf",&x[i],&y[i]);
22     } 
23     memset(mark,0,sizeof(mark));
24     for(int i=0;i<n;i++){
25         for(int j=i+1;j<n;j++){
26             double x1=x[i],x2=x[j],y1=y[i],y2=y[j];
27             double a=(x1*y2-y1*x2)/(x1*x2*x2-x1*x1*x2);
28             if(a>=0)continue;//抛物线,开口向下
29             double b=(y1-a*x1*x1)/x1;
30             for(int k=0;k < n;k++){
31                 if(abs(y[k]-x[k]*x[k]*a-x[k]*b)<=ex){
32                 //误差范围内,近似为0
33                     //if(!(mark[i][j]&state[k]))mark[i][j]|=state[k];
34                     mark[i][j]+=state[k];
35                 } 
36             }     
37         }
38     }
39     memset(f,127,sizeof(f));
40     int inf=f[1];f[0]=0;
41     int lim=(1<<n)-1;//最终状态是11111111111
42     
43      for(int i=0;i<=lim;i++){//枚举每种状态
44          if(f[i]!=inf){
45              for(int j=0;j<n;j++){
46                  if(!(i&state[j])){//当前状态没有j 
47                      int now=i|state[j];
48                      f[now]=min(f[i]+1,f[now]);
49                      //可以从f[i]再射出一只 
50                     for(int k=j+1;k<n;k++){
51                         int noww=i|mark[j][k];//j,k抛物线的所有猪 
52                         f[noww]=min(f[i]+1,f[noww]);
53                     }
54                  }
55              }
56          } 
57          
58      }
59     printf("%d
",f[lim]);
60 }
61 
62 int main(){
63     int T;
64     scanf("%d",&T);
65     state[0]=1;
66     for(int i=1;i<=20;i++)
67      state[i]=state[i-1]<<1;
68     while(T--){
69         work();
70     }return 0;
71 }
View Code

【总结】

当题中的问题状态可以转换成0,1表示的时候,可以考虑用状压dp,不过要正确使用二进制

原文地址:https://www.cnblogs.com/Danzel-Aria233/p/7724241.html