noip2016 愤怒的小鸟 luogu 2831

http://www.elijahqi.win/archives/535
题目描述

Kiana最近沉迷于一款神奇的游戏无法自拔。

简单来说,这款游戏是在一个平面上进行的。

有一架弹弓位于(0,0)处,每次Kiana可以用它向第一象限发射一只红色的小鸟,小鸟们的飞行轨迹均为形如y=ax2+bxy=ax^2+bxy=ax​2​​+bx的曲线,其中a,b是Kiana指定的参数,且必须满足a<0。

当小鸟落回地面(即x轴)时,它就会瞬间消失。

在游戏的某个关卡里,平面的第一象限中有n只绿色的小猪,其中第i只小猪所在的坐标为(xi,yi)。

如果某只小鸟的飞行轨迹经过了(xi,yi),那么第i只小猪就会被消灭掉,同时小鸟将会沿着原先的轨迹继续飞行;

如果一只小鸟的飞行轨迹没有经过(xi,yi),那么这只小鸟飞行的全过程就不会对第i只小猪产生任何影响。

例如,若两只小猪分别位于(1,3)和(3,3),Kiana可以选择发射一只飞行轨迹为y=−x2+4xy=-x^2+4xy=−x​2​​+4x的小鸟,这样两只小猪就会被这只小鸟一起消灭。

而这个游戏的目的,就是通过发射小鸟消灭所有的小猪。

这款神奇游戏的每个关卡对Kiana来说都很难,所以Kiana还输入了一些神秘的指令,使得自己能更轻松地完成这个游戏。这些指令将在【输入格式】中详述。

假设这款游戏一共有T个关卡,现在Kiana想知道,对于每一个关卡,至少需要发射多少只小鸟才能消灭所有的小猪。由于她不会算,所以希望由你告诉她。

输入输出格式

输入格式:

第一行包含一个正整数T,表示游戏的关卡总数。

下面依次输入这T个关卡的信息。每个关卡第一行包含两个非负整数n,m,分别表示该关卡中的小猪数量和Kiana输入的神秘指令类型。接下来的n行中,第i行包含两个正实数(xi,yi),表示第i只小猪坐标为(xi,yi)。数据保证同一个关卡中不存在两只坐标完全相同的小猪。

如果m=0,表示Kiana输入了一个没有任何作用的指令。

如果m=1,则这个关卡将会满足:至多用⌈n3+1⌉\left \lceil \frac{n}{3} + 1 \right \rceil⌈​3​​n​​+1⌉只小鸟即可消灭所有小猪。

如果m=2,则这个关卡将会满足:一定存在一种最优解,其中有一只小鸟消灭了至少⌊n3⌋\left \lfloor \frac{n}{3} \right \rfloor⌊​3​​n​​⌋只小猪。

保证1<=n<=18,0<=m<=2,0< xi,yi<10,输入中的实数均保留到小数点后两位。

上文中,符号⌈x⌉\left \lceil x \right \rceil⌈x⌉和⌊x⌋\left \lfloor x \right \rfloor⌊x⌋分别表示对c向上取整和向下取整

输出格式:

对每个关卡依次输出一行答案。

输出的每一行包含一个正整数,表示相应的关卡中,消灭所有小猪最少需要的小鸟数量

输入输出样例

输入样例#1:

2
2 0
1.00 3.00
3.00 3.00
5 2
1.00 5.00
2.00 8.00
3.00 9.00
4.00 8.00
5.00 5.00
输出样例#1:

1
1
输入样例#2:

3
2 0
1.41 2.00
1.73 3.00
3 0
1.11 1.41
2.34 1.79
2.98 1.49
5 0
2.72 2.72
2.72 3.14
3.14 2.72
3.14 3.14
5.00 5.00
输出样例#2:

2
2
3
输入样例#3:

1
10 0
7.16 6.28
2.02 0.38
8.33 7.78
7.68 2.09
7.46 7.86
5.77 7.44
8.24 6.72
4.42 5.11
5.42 7.79
8.15 4.99
输出样例#3:

6
洛谷评测48ms 本蒟蒻感觉应该挺快的了吧。(洛谷看到还有0ms 的神犇qwq)

这题是状态压缩

放一个80分的程序 应该是有一定的问题的,按理说洛谷应该可以跑85才对,有一个点wa了 蒟蒻我看不出来ORZ

思路:

因为一共18只鸟所以我们把18只鸟用二进制表示最后十进制储存

1表示编号为这一位的鸟还存活0表示已经挂了 因为有小数 注意判断下精度

开结构体a[i][j]表示编号i j两只鸟 exist表示是否一条抛物线可以打下两只鸟,a,b分别表示二次函数里的a和b

b数组的下标存的是状态 若为1表示这种状态存在

正式开始记忆化搜索:for (;s1>=1;s1=s&(s1-1)) 这个式子十分巧妙的把当前状态所有存在的鸟被打后的状态一一枚举出来不重不漏,好好推导一下

因为s1比s一定要小 所以直接十进制减法就可以代替状态里的鸟的存活状态的加减运算

提示:状态压缩时,假如只能打一只鸟b数组也要认为是1 就是认为打一只鸟也可以

鸟的标号从0开始比较好做,要么就在状压的时候统一好

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 20
#define N1 270000                   
int b[N1],data[N1],n,m;
double x[N],y[N];
struct node{
    int exist;double a,b;
}a[N][N];
inline bool judge(double a,double b){
    if (std::abs(a-b)<=1e-10) return true;else return false;
}
inline void work(int i,int j){
    if (judge(x[i],x[j])) {
        a[i][j].exist=0;a[i][j].a=a[i][j].b=0;return ;
    }
    double tmpa=(y[i]*x[j]-y[j]*x[i])/(x[i]*x[j]*(x[i]-x[j]));
    double tmpb=y[i]/x[i]-(y[i]*x[j]-y[j]*x[i])/(x[j]*(x[i]-x[j]));
    a[i][j].a=tmpa;a[i][j].b=tmpb;
    if (tmpa>0){
        a[i][j].exist=0;return ;
    }
    a[i][j].exist=1;
}
//for (int s1=s;s1>=1;s1=s&(s1-1)) 
void f(int s){
    if (b[s]) {data[s]=1;return;}
    int s1=s;s1=s&(s1-1);
    for (;s1>=1;s1=s&(s1-1)){
        if (data[s1]==50) f(s1);
        if (data[s-s1]==50) f(s-s1);
        data[s]=std::min(data[s],data[s1]+data[s-s1]);
    }
}
int main(){
//  freopen("ex1.in","r",stdin);
//  freopen("ex1.out","w",stdout);
    int T;
    scanf("%d",&T);
    while (T--){
        scanf("%d%d",&n,&m);
        for (int i=0;i<n;++i) scanf("%lf%lf",&x[i],&y[i]);
        memset(b,false,sizeof(b));
        for (int i=0;i<n;++i)
            for (int j=0;j<n;++j) work(i,j); 
    //  for (int i=0;i<n;++i)
    //      for (int j=i+1;j<n;++j) printf("%f %f\n",a[i][j].a,a[i][j].b);
        for (int i=0;i<n;++i) b[1<<i]=1;
        for (int i=0;i<n;++i){  
            for (int j=i+1;j<n;++j){
                int tmp=0;
                if (a[i][j].exist){
                    tmp+=(1<<i)+(1<<j);
                    for (int z=j+1;z<n;++z){
                        if (i!=z&&j!=z)
                            if (a[z][i].exist&&a[i][z].a&&a[i][j].a&&judge(a[i][z].a,a[i][j].a)&&judge(a[i][j].b,a[i][z].b)) {
                                tmp+=(1<<z);//  if (tmp==25) printf("%d %d %d\n",i,j,z);
                            }
                    }

                    b[tmp]=true;
                }
            }
        }   
        for (int i=1;i<=(1<<n)+3;++i) data[i]=50;
        f((1<<n)-1);
        printf("%d\n",data[(1<<n)-1]);
    //  for (int i=1;i<=1050;++i) if (b[i]) printf("%d\n",i);
        //printf("asffd\n");
    }
    return 0;
}

这个是满分的程序:

b1数组表示i,j两只鸟之间连成的抛物线打完鸟后最终平面内还剩余和不剩余的鸟 这种状态转成十进制储存在 b1数组中

这种做法因为我们肯定每次钦定都要打第一只鸟,然后枚举

s&((1<


#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 20
#define N1 270000                   
int b1[N][N],b[N1],data[N1],n,m;
double x[N],y[N];
struct node{
    int exist;double a,b;
}a[N][N];
inline bool judge(double a,double b){
    if (std::abs(a-b)<=1e-10) return true;else return false;
}
inline void work(int i,int j){
    if (judge(x[i],x[j])) {
        a[i][j].exist=0;a[i][j].a=a[i][j].b=0;return ;
    }
    double tmpa=(y[i]*x[j]-y[j]*x[i])/(x[i]*x[j]*(x[i]-x[j]));
    double tmpb=y[i]/x[i]-(y[i]*x[j]-y[j]*x[i])/(x[j]*(x[i]-x[j]));
    a[i][j].a=tmpa;a[i][j].b=tmpb;
    if (tmpa>0){
        a[i][j].exist=0;return ;
    }
    a[i][j].exist=1;
}
//for (int s1=s;s1>=1;s1=s&(s1-1)) 
/*void f(int s){
    if (b[s]) {data[s]=1;return;}
    int s1=s;s1=s&(s1-1);
    for (;s1>=1;s1=s&(s1-1)){
        if (data[s1]==50) f(s1);
        if (data[s-s1]==50) f(s-s1);
        data[s]=std::min(data[s],data[s1]+data[s-s1]);
    }
}*/
void f(int s){
    if (s==0) {data[s]=0;return;}
    int rest=0;for (int i=0;i<n;++i) if(s&(1<<i))++rest;
    if (rest==1){data[s]=1;return;} 
    if (rest==2) if(b[s]) {data[s]=1;return;}else {data[s]=2;return;}
    int i=0;while(!(s&(1<<i))) ++i;
    for (int j=0;j<n;++j)
        if (b1[i][j]) {
            if (data[s&((1<<n)-1-b1[i][j])]==50) f(s&((1<<n)-1-b1[i][j]));
            data[s]=std::min(data[s],data[s&((1<<n)-1-b1[i][j])]+1);
        }
}
int main(){
    freopen("ex1.in","r",stdin);
//  freopen("ex1.out","w",stdout);
    int T;
    scanf("%d",&T);
    while (T--){
        scanf("%d%d",&n,&m);
        for (int i=0;i<n;++i) scanf("%lf%lf",&x[i],&y[i]);
        memset(b,false,sizeof(b));memset(b1,0,sizeof(b1));
        for (int i=0;i<n;++i)
            for (int j=0;j<n;++j) work(i,j); 
    //  for (int i=0;i<n;++i)
    //      for (int j=i+1;j<n;++j) printf("%f %f\n",a[i][j].a,a[i][j].b);
        for (int i=0;i<n;++i) b[1<<i]=1,b1[i][i]=1<<i;
        for (int i=0;i<n;++i){  
            for (int j=i+1;j<n;++j){
                int tmp=0;
                if (a[i][j].exist){
                    tmp+=(1<<i)+(1<<j);
                    for (int z=j+1;z<n;++z){
                        if (i!=z&&j!=z)
                            if (a[z][i].exist&&a[i][z].a&&a[i][j].a&&judge(a[i][z].a,a[i][j].a)&&judge(a[i][j].b,a[i][z].b)) {
                                tmp+=(1<<z);//  if (tmp==25) printf("%d %d %d\n",i,j,z);
                            }
                    }
                    b1[i][j]=tmp;
                    b[tmp]=true;
                }
            }
        }   
        for (int i=1;i<=(1<<n);++i) data[i]=50;
        f((1<<n)-1);
        printf("%d\n",data[(1<<n)-1]);
    //  for (int i=1;i<=1050;++i) if (b[i]) printf("%d\n",i);
        //printf("asffd\n");
    }
    return 0;
}

递推:不好想,我脑子不太好,先贴一个leoly (ORZ)的

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 20
#define eps 1e-10
using namespace std;
struct aa{double x, y;}a[N];
int T, n, temp, bin[N], x, w[N][N], f[262150];
double x1, y1, x2, y2, A, B;
inline double abs1(double a){return max(a, -a);}
int main(){
    scanf("%d", &T);
    bin[0]=1; for(int i=1; i<=18; i++)bin[i]=bin[i-1]<<1;
    for(int owo=1; owo<=T; owo++){
        scanf("%d%d", &n, &temp);
        for(int i=1; i<=n; i++)scanf("%lf%lf", &a[i].x, &a[i].y);
        for(int i=1; i<=n-1; i++)
            for(int j=i+1; j<=n; j++){
                x1=a[i].x; y1=a[i].y; x2=a[j].x; y2=a[j].y;
                if(abs1(x1-x2)<eps){w[i][j]=bin[n]-1; continue;}
                A=(x1*y2-x2*y1)/(x1*x2*(x2-x1)); B=(y1-A*x1*x1)/x1;
                if(A>-eps){w[i][j]=bin[n]-1; continue;}
                w[i][j]=bin[n]-1;
                for(int k=1; k<=n; k++)
                    if(abs1(A*a[k].x*a[k].x+B*a[k].x-a[k].y)<eps)w[i][j]-=bin[k-1];
            }
        f[0]=0;
        for(int i=1; i<=bin[n]-1; i++){
            for(int j=n; j; j--)if(i&bin[j-1]){x=j; break;}
            f[i]=f[i-bin[x-1]]+1;
            for(int j=1; j<=x-1; j++)f[i]=min(f[i], f[i&w[j][x]]+1);
        }
        printf("%d\n", f[bin[n]-1]);
    }
    return 0;
}

2017.11.1 update 用真题当作模拟赛 然而怎么也写不满 现在还是95分 先弃坑待填

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define N 20
#define eps 1e-6
#define inf 0x3f3f3f3f
struct node{
    double x,y;
}bird[N];int dp[1<<N],n,bin[N],b1[N][N],T,m;
void f(int s){
    if (s==0){dp[s]=0;return;}if (dp[s]!=inf) return;
    int num=0;
    for (int i=0;i<n;++i) if (bin[i]&s)++num;
    if (num==1){dp[s]=1;return;}
    for (int i=0;i<n;++i){
        if (s&bin[i]){
            for (int j=0;j<n;++j){
                int s1=b1[i][j];if (!s1) continue;int s2=s^s1;f(s2);
                dp[s]=min(dp[s],1+dp[s2]);
            }break;
        }
    }
}
int main(){
//  freopen("angrybirds.in","r",stdin);
    scanf("%d",&T);for (int i=0;i<N;++i) bin[i]=1<<i;
    while (T--){
        scanf("%d%d",&n,&m);
        for (int i=0;i<n;++i) scanf("%lf%lf",&bird[i].x,&bird[i].y);memset(dp,0x3f,sizeof(dp));memset(b1,0,sizeof(b1));
        for (int i=0;i<n;++i) b1[i][i]=1<<i;
        for (int i=0;i<n;++i){
            for (int j=i+1;j<n;++j){
                int x1=i,x2=j;
                if (abs(bird[x1].x-bird[x2].x)<=eps) continue;
                double a=(bird[x1].y*bird[x2].x-bird[x2].y*bird[x1].x)/(bird[x1].x*bird[x2].x*(bird[x1].x-bird[x2].x));
                double b=bird[x1].y/bird[x1].x-(bird[x1].y*bird[x2].x-bird[x2].y*bird[x1].x)/(bird[x2].x*(bird[x1].x-bird[x2].x));
                if (a>=0) continue;int s=bin[x1]|bin[x2];
                for (int k=j+1;k<n;++k){
                    if (k==x1||k==x2) continue;
                    if (abs(bird[k].y-a*bird[k].x*bird[k].x-b*bird[k].x)<=eps) s|=bin[k];
                }dp[s]=1;b1[i][j]=s;
            }
        }f(bin[n]-1);printf("%d\n",dp[bin[n]-1]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值