• 2014年广州区域赛i题解


    2014年广州区域赛i题解

    题意

    从给的n根木棍中选择一部分组成若干个三角形,问最大三角形总面积为多少。

    笺释

    样例2懵了半个小时才转过来弯,原来13是比12大的,在我的印象里13一直都比12小才对,为什么呢,可能是因为西方13是不吉利的数字所以太少出现了吧=。=
    这道题能不能贪呢,我这边是证不出来的,而我的话如果证不出来贪心就不会用的。

    但是有一点是确定的

    先选出来3个组成一个最大的三角形,再组成第二大的三角形,这样总面积最大是没有保证的。

    但是这给了我们提示

    也是dp的惯例了,如果局部贪心保证不了,就索性记录所有情况,然后用之前的情况更新之后的就可以。

    我的思路是这样的

    先预处理出所有组成1个,2个,3个,4个(最多4个)三角形所用木棍的所有情况,用二进制集合的形式加以表示,具体是这样的:

    void init()
    {
        //所有长度为3 6 9  12的子集
        for(int i=1;i<=4;i++)
        {
            //k为长度
            //i为三角形个数
            int k=getq[i];
            int comb=(1<<k)-1;
            while(comb<(1<<12))
            {
                box[i][++sum[i]]=comb;
                int x=comb& -comb,y=comb+x;
                comb=((comb& ~y)/x>>1)|y;
            }
        }
    }
    

    这种表示办法在挑战程序设计竞赛上是有严格推导的,有兴趣的可以去看看呢。
    然后就是状压dp的常规思路了,枚举所有状态情况,根据之前的状态更新之后的状态。

            }
            for(int i=(1<<3)-1;i<=(1<<n)-1;i++)
            {
                int num=cal(i);
                if(!(num%3))
                {
                    int k=num/3;
                    for(int j=1;j<=sum[k-1];j++)
                    {
                        int r=box[k-1][j];
                        if((r&i)==r)//这里是判断用来更新状态i的状态r是i的子集
                        {
                            if(okc(i^r))//这里是用取补集的办法得到新状态所增加的三角形
                            {
                                        dp[i]=max(dp[i],dp[r]+counts(i^r));
                                    ans=max(ans,dp[i]);
                            }
                        }
                    }
                }
            }
    

    为什么不用搜索呢

    这个题数据范围其实很像是dfs的范围,但是至少对于我来说dfs这个角度不太好想,因为这个题多多少少是可以用dp的递推结构的,尽管最多也就是推四步而已,当然我并不是在说递推是dp的专利,实际上用dfs应该也是可以写的,不过最终思想应该都是一个递推思想,我想说的是,实际上dfs和dp之间并没有一道很明显的分界线,很多时候是可以混用的,很多时候dfs不太熟悉或者不太好想转化成dp是很好的。
    我说话可真绕。=。=

    完整代码

    #include<bits/stdc++.h>
    using namespace std;
    int n;
    int sum[4];
    int box[5][1<<13];
    int a[15];
    int getq[5]={0,3,6,9,12};
    double dp[1<<13];
    double  getnum(int a1,int a2,int a3)
    {
        double p=(a1+a2+a3)*1.0/2;
        return sqrt(p*(p-a1*1.0)*(p-a2*1.0)*(p-a3*1.0));
    }
    bool ok(int a1,int a2,int a3)
    {
        //两边之和大于第三边
        if(a1+a2<a3)
        {
            return false;
        }
        if(a1+a3<a2)
        {
            return false;
        }
        if(a3+a2<a1)
        {
            return false;
        }
        return true;
    }
    void init()
    {
        //所有长度为3 6 9  12的子集
        for(int i=1;i<=4;i++)
        {
            //k为长度
            //i为三角形个数
            int k=getq[i];
            int comb=(1<<k)-1;
            while(comb<(1<<12))
            {
                box[i][++sum[i]]=comb;
                int x=comb& -comb,y=comb+x;
                comb=((comb& ~y)/x>>1)|y;
            }
        }
    }
    int cal(int x)
    {
        int ret=0;
        for(int i=1;i<=n;i++)
        {
            if(x>>(i-1)&1)
               {
                    ret++;
               }
        }
        return ret;
    }
    bool okc(int x)
    {
        int ret[4],p=1;
        for(int i=1;i<=n;i++)
        {
            if(x>>(i-1)&1)
               {
                    ret[p++]=i;
               }
        }
        if(ok(a[ret[1]],a[ret[2]],a[ret[3]]))
        {
            return true;
        }
        return false;
    }
    double counts(int x)
    {
        int ret[4],p=1;
        for(int i=1;i<=n;i++)
        {
            if(x>>(i-1)&1)
               {
                    ret[p++]=i;
               }
        }
        return getnum(a[ret[1]],a[ret[2]],a[ret[3]]);
    }
    int main()
    {
        sum[0]=1;
        init();
        while(scanf("%d",&n),n)
        {
            memset(dp,0,sizeof(dp));
            double ans=0;
            for(int i=1;i<=n;i++)
            {
                scanf("%d",&a[i]);
            }
            for(int i=(1<<3)-1;i<=(1<<n)-1;i++)
            {
                int num=cal(i);
                if(!(num%3))
                {
                    int k=num/3;
                    for(int j=1;j<=sum[k-1];j++)
                    {
                        int r=box[k-1][j];
                        if((r&i)==r)
                        {
                            if(okc(i^r))
                            {
                                dp[i]=max(dp[i],dp[r]+counts(i^r));
                                ans=max(ans,dp[i]);
                            }
                        }
                    }
                }
            }
            printf("%.2f
    ",ans);
        }
    }
    
    
  • 相关阅读:
    [转] C# DataTable 导出 Excel 进阶 多行表头、合并单元格、中文文件名乱码
    【转】sql语句精选二
    【转】sqlserver游标概念与实例全面解说
    按多个关键字查询(sql)
    Asp.net使用repeater控件动态添加、删除一行
    SQL SERVER 导入、导出数据到Exce(使用OpenRowset,、OpenDataSource函数)以及访问远程数据库(openrowset/opendatasource/openquery)
    对 Dflying Chen 提到的Edit GridView Using CheckBoxes 进行一个小改造
    软件开发专业技术名词的解释
    (总结)如何为windows服务添加安装程序
    软件开发过程(RUP概述) 转
  • 原文地址:https://www.cnblogs.com/SoniciSika/p/9034198.html
Copyright © 2020-2023  润新知