题目链接: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 }
【总结】
当题中的问题状态可以转换成0,1表示的时候,可以考虑用状压dp,不过要正确使用二进制