zoukankan      html  css  js  c++  java
  • suac 2012新生赛 F切水果

    推荐技术公众号:不爱睡觉的大猪

    题意:

    描述

       Lrc是校队里面的总所周知的全才王,他不仅是一个excelent acmer,也不仅是一个chess master,更是一个crazy game player。
    切水果,正是他最喜欢玩的手机游戏之一。为了避免有人没玩过,下面介绍一下Lrc是怎么玩这个游戏的~-~
    1) 整个屏幕是一个笛卡尔坐标系。
    2) 在某个时刻,屏幕上会出现灰常多的水果,西瓜、草莓神马的,当然还有一种,炸弹。
    3) 每个时刻用手指在屏幕上划过,切中水果可以得到一定得分数,切到炸弹就要扣分。
    4) 每个时刻最多只能切一刀,至于一刀切多少个可以自由控制。
    5) 第t时刻没有被切的水果,将不会出现在下一个时刻。
    6) 由于Lrc 骨骼奇精,所以手指在屏幕上只能划直线(线段),也就是说只有在同一直线上的水果他才能在同一时间内切到。(友情提醒,直线不仅是斜的,也可以是水平和垂直的哦)。
       由于Lrc比较神,每次他总能得到最高分。嘿嘿,你想要成为未来的Acmer吗?那就必须帮Lrc算出他的分数咯O(∩_∩)O~
    ----------------------------------------------------------------------------------------------------------------------------------------
       PS:记住,Acmer要有永不言弃的精神,花一天过了这个题,值得了~~~~~
    
    (出题人Ly)
    

     

    输入格式

        第一行3个整数n(n<=150),m(m<=20),k(0=<k<=1e9+7),分别代表这一局出现的水果(炸弹)数,水果种类,以及每切中一个炸弹要扣的分数。
    紧接着一行m个整数v1,v2,v3….vm(0<=vi<=10000),分别代表m种水果的分数。
    接下来n行分别代表一个水果(炸弹),每行4个整数x,y,p,t(x,y代表该水果的坐标,p代表该水果得种类(p=0时为炸弹),t代表该水果出现的时刻)(-1000<=x,y<=1000,0<=p<=m,1<=t<=90)。
    

     

    输出格式

    若Lrc 得到的分数小于或者等于零,请输出“Poor Lrc!!!”,因为他将要受到强神的BS;
    否则,输出“God Lrc, you’ve got S points!”,S是Lrc得到的分数。
    

     

    输入样例

    sample#1:
    1 1 10
    10
    0 0 1 1
    
    sample#2:
    6 3 20
    1 2 10
    0 1 2 1
    100 100 2 2
    0 2 0 1
    0 3 3 1
    1 1 3 2
    0 0 1 1
    
    

     

    输出样例

    sample#1:
    God Lrc, you’ve got 10 points!
    
    sample#2:
    God Lrc, you’ve got 22 points!
    

     

    Hint

    样例中的sample#1,sample#2不属于输入输出的一部分,只是说明其不是一个输入和输出。

    Attention: 炸弹当一个水果就行了!!切到炸弹不结束!!

     

     

    显然是几何,关于直线和点的。这题还是水题,只要不想复杂了就好,其水题的本质是因为数据规模太小,直接暴力枚举,而且期间不需要long long变量,全部用int

    1.对所有的水果从0到n-1标号,按时间从小到大排序,每次只能处理同一时间出现的水果,也就是按区间处理,接下来是怎么处理一个区间(相同时间)的水果

    2.注意一点,每个时间只能切一刀,一刀只能切一条直线上的水果,然后不一定要切完整条直线上,可以只切这个直线上的一段子序列,甚至可以不切(当切的最优解都小于0的时候,那倒不如不切,这样值为0,才是最优解,我就是差了这个判断WA了一次,其实可以将最优值初始化为0,那么就不用做这个判断了)

    说到这里就想到了什么呢?先找到所有可能的直线,把一条直线上的点全部找出来,保存下来还要按直线位置排好,然后最大子序列求和(因为可以切直线上的一部分,只能一次,显然就是最大子序列和),但是细想这种算法显然是不行的,太麻烦,实现起来很难受,再看看数据规模不大,其实只要枚举所有的起点和终点(我的代码中起点和终点是不同的),那么就得到一条线段,再枚举所有其他的点在不在线段内(不在延长线),然手算出这条线段的和,这样子其实无形中解决了子序列和的问题,因为枚举起点和终点其实不止枚举了所有直线还枚举所有的子序列

    计算完线段的和并找到了最大值,别忘了这是线段,没有包含只切一个点的情况,所有逐一判断只切一个点的时候权会不会更大,然后更新

    最后,就得到了切下去的时候的最优解,看看这个最优解是否大于0,是的话那它当然是最优解,不是的话,显然不应该切下去,直接抛弃答案,认为这些切割的得分为0

     

    因为整个代码其实都是枚举的多,要说用了知识点就两个

    1.计算任意两个点(同一时间的任意两个点)的斜率,斜率用分数保存,这样可以避免精度问题,斜率不存在的用INF表示

    2.判断一个点c在不在线段ab上(c不是a,b两个端点)

    判别方法:ac的斜率和ab的斜率相同,两个线段斜率相等且过一个公共点a,那么3点共线。单单3点共线还不行,要判断不在延长线和反向延长线上,那就是判断点c在线段ab作为对角线的矩形内部,具体看代码中的on_segment(i,j,k)函数

     

    代码写的匆忙,也没有整理,凑合着看吧,本身题目不难

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define N 160
    #define M 30
    #define INF 0x3f3f3f3f
    struct fff  //水果信息
    {
        int x,y,v,t;
    }a[N],b[N]; //v是水果的价值,一开始是种类,再变为价值
    struct kkk  //建图保存任意两点的斜率,用分数保存
    {
        int y,x;
    }g[N][N];
    int n,mm,K;  //水果个数,种类数,炸弹价值
    int m[M];
    
    int min(int x ,int y)
    { return x<y?x:y; }
    int max(int x , int y)
    { return x>y?x:y; }
    
    
    int cmp(struct fff p , struct fff q)
    { return p.t<q.t; }
    /*
    void printfff()
    {
        printf("______________\n");
        for(int i=0; i<n; i++)
            printf("%d %d %d %d\n",a[i].x,a[i].y,a[i].v,a[i].t);
        printf("______________\n");
        return ;
    }
    */
    int on_segment(int i , int j ,int k)
    {//判断点k在不在线段(i,j)上
        //先判断斜率是否相同
        int y1,y2,x1,x2;
        y1=g[i][j].y;  x1=g[i][j].x;
        y2=g[i][k].y;  x2=g[i][k].x;
        if(y1*x2!=y2*x1)  return 0;  //斜率不同
    
        if( min(a[i].x,a[j].x)<=a[k].x && a[k].x<=max(a[i].x,a[j].x) && 
            min(a[i].y,a[j].y)<=a[k].y && a[k].y<=max(a[i].y,a[j].y) )
            return 1;
        else 
            return 0;
    }
    int max_sum(int p , int q)  //求[p,q]区间内的最大值,这一区间的水果是同一时间
    {
        int maxsum,sum;
        int i,j;
        /*=======保存任意两点的斜率========*/
        memset(g,0,sizeof(g));
        for(i=p; i<=q; i++)
            for(j=p; j<=q; j++)
                if(i!=j)  //求任意两个水果i,j的斜率
                {
                    int yy,xx;
                    yy=a[i].y-a[j].y;
                    xx=a[i].x-a[j].x;
                    if(xx==0)  //斜率不存在,设为无穷大
                    { g[i][j].y=INF; g[i][j].x=1; } 
                    else  //斜率存在,用分数保存
                    { g[i][j].y=yy; g[i][j].x=xx; }
                }
    /*
        printf("该区间内任意两点(不同)的斜率\n");
        for(int i=p; i<=q; i++)
            for(int j=p; j<=q; j++)
                if(i!=j)
                    printf("(%d,%d)  %d\\%d\n",i,j,g[i][j].y,g[i][j].x);
    */
        /*=======保存任意两点的斜率========*/
    
        //枚举所有的起点和终点,然后判断那些点在这个线段上(注意是线段)
        //然后把线段上的点的和全部求出来,这样子其实已经解决了子序列求和的问题
        //关键是怎么判断一点在一个线段上
        maxsum=-INF;
        for(int i=p; i<=q; i++)
            for(int j=p; j<=q; j++)
                if(i!=j)  //枚举线段的所有起点和终点
                {
                    sum=a[i].v+a[j].v;
                    //printf("枚举的起点和终点为:  %d   %d  其实和为%d+%d=%d\n",i,j,a[i].v,a[j].v,sum);
                    for(int k=p; k<=q; k++)  //枚举所有可能的点k,看是否在线段上
                        if(k!=i && k!=j)
                        {
                            if( on_segment(i,j,k) ) //点k在线段(i,j)上
                            {
                                //printf("%d在线段上\n",k);
                                sum+=a[k].v;
                            }
                        }
                    //printf("最终该线段的和:%d\n",sum);
                    if(sum>maxsum) maxsum=sum;
                }
        //printf("======只切单独的一个点======\n");
        for(int i=p; i<=q; i++)
            if(a[i].v>maxsum) maxsum=a[i].v;
        if(maxsum<0)  return 0;  //如果切了分数反而小于0,那么就不要切
        return maxsum;
    }
    void solve()
    {
        int sum;
        int i,j;
        a[n].t=0;  //保险处理
        sum=0;
        for(i=0; i<n; )  //扫描所有的水果
        {
            int t=a[i].t;
            for(j=i; j<n; j++)
                if(a[j+1].t!=t) break;
            //得到区间[i,j],闭区间,是相同时间的,那么就求这段时间里面的最大值
            //printf("打印区间[%d , %d]\n",i,j);
            int tmp=max_sum(i,j);
            //printf("该区间的最大值%d\n",tmp);
            sum+=tmp;
            i=j+1; //从下一个点开始
        }
        //printf("最后的得分%d\n",sum);
        if(sum<=0)  printf("Poor Lrc!!!\n");
        else        printf("God Lrc, you’ve got %d points!\n",sum);
        return ;
    }
    int main()
    {
        while(scanf("%d%d%d",&n,&mm,&K)!=EOF)
        {
            for(int i=1; i<=mm; i++)  //每种水果的价值
                scanf("%d",&m[i]);
            m[0]=-K;  //炸弹价值
    
            for(int i=0; i<n; i++)  //n个水果的信息
            {
                scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].v,&a[i].t);
                a[i].v=m[a[i].v];
            }
            sort(a,a+n,cmp);
            //printfff();
            solve();
        }
        return 0;
    }

     

  • 相关阅读:
    Go语言之Go 语言函数
    Go语言之Go 语言循环语句
    Go语言之Go 语言条件语句
    Go语言之Go 语言运算符
    Go语言之GO 语言注释
    Go语言之Go 语言类型别名
    7.19 PDO(php data object-php数据对象)数据库抽象层
    7.15 原生js写ajax
    7.15 文件打开后点击打开下级文件
    6.28 js和php数组去重
  • 原文地址:https://www.cnblogs.com/scau20110726/p/2798457.html
Copyright © 2011-2022 走看看