6.6 练习题解

很不意外得考得不好呀。。。总结一下吧

A.挡光线

问题描述

坐标原点处有一光源,向第一象限发射光,光线成发散状,边缘处用OA,OB两条射线来表示。现在给你n个正方形,请你用它们将光线全部挡住,正方形的位置可以任意调整,但正方形的边必须平行于坐标轴,如下图所示。现在要求挡住后阴影部分的面积要尽可能大,请求出这个最大面积。这个图片是用来麻人的

输入格式

第一行,一个整数N,表示正方形的个数。
第二行,四个实数,Xa,Ya,Xb,Yb,(Xa,Ya)表示点A的坐标,(Xb,Yb)表示点B的坐标
第三行,N个空格间隔的实数,分别表示N个正方形的边长。

输出格式

一个实数,表示所求结果,保留3个小数位

数据范围

对于100%的数据,0

题解:

正方形的对角线在一条直线时容易知道此时的面积最大,建系计算两个直线斜率,而剩余面积用三角形面积减去所有正方形面积和的一半即可。解释见代码。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int main(){
    double s=0,k=0,k1,k2,x,xa,len,sum=0,xb,ya,yb;
    int n,i;
    scanf("%d",&n);
    scanf("%lf%lf%lf%lf",&xa,&ya,&xb,&yb);
    k1=ya/xa;
    k2=yb/xb;//算斜率 
    if(k1<k2)swap(k1,k2);
    for(i=1;i<=n;i++){
        scanf("%lf",&len);
        s+=len*len/2;//所有正方形面积的一半 
        sum+=len;//所有正方形边长和 
    }
    x=sum*(k2+1)/(k1-k2);//x为k较大的射线上正方形顶点的横坐标 
    printf("%.3lf",sum*(x*k1+(x+sum)*k2)/2+x*x*k1/2-s-(x+sum)*k2*(x+sum)/2);//用大面积减去小面积可解出答案 
}

B.彩色方块

问题描述

何老板最近在玩一款叫“彩色方块”的小游戏,游戏虽然简单,但何老板仍旧乐此不疲。
游戏中有n个彩色方块成一排,方块的颜色用字母表示,给出目标排列,只要把它们排成跟目标一样的排列,就算过关。每次操作只能交换相邻两个方块。
给出一关游戏,何老板想知道,最少操作几次就能过关,请你帮他计算最少所需的操作步数。

扫描二维码关注公众号,回复: 1565751 查看本文章

输入格式

第一行,一个整数n表示方块的数量
第二行,n个大写字母,表示一开始方块的排列情况。每个字母表示对应方块的颜色。
第三行,n个大写字母,表示方块的目标排列情况。

输出格式

一行,一个整数,表示计算结果。
数据保证有解。

数据范围

对于100%的数据:2<=n<=1,000,000

当时看到了以为是字符串算法。。技艺不精呀。

题解:

我们将原序列的每个字母从左至右标上序号,然后再将目标序列根据原序列一一对应的字母标上同样的号:

AACB->BACA
1 2 3 4->4 1 3 2(更优)或是4 2 3 1

容易得到比较近的更优,所以使用队列储存每种字母出现的顺序相对应的编号,然后将问题转化为求逆序对。

代码:

#include<iostream>
#include<cstdio>
#include<queue>
#define LL long long
using namespace std;
const int maxn=1000010;
queue <int> q[120];
int n,tree[maxn];
char a[maxn];
int p[maxn];
char read(){
    char c=getchar();
    while(c<'A'||c>'Z')c=getchar();
    return c;
}
int lowbit(int x){
    return x&(-x);
}
int getsum(int x){
    int sum=0;
    for(;x>0;x-=lowbit(x))sum+=tree[x];
    return sum;
}
void modify(int x){
    for(;x<=n;x+=lowbit(x))tree[x]++;
}
int main(){
    int i,j;LL ans=0;
    char x;
    scanf("%d",&n);
    for(i=1;i<=n;i++)a[i]=read();
    for(i=1;i<=n;i++){
        x=read();
        q[x].push(i);//存入队列 
    }
    for(i=1;i<=n;i++){
        p[q[a[i]].front()]=i;//更新目标序列 
        q[a[i]].pop();
    }
    for(i=1;i<=n;i++){
        ans+=getsum(n)-getsum(p[i]);
        modify(p[i]);
    }//使用树状数组计算逆序对个数 
    printf("%lld",ans);
    return 0;
}

C.营养午餐

问题描述

信竞班的同学们拼命学习,编程刷题到了废寝忘食的地步,看着大家日渐消瘦,这让何老板很是担心。于是何老板打算给大家提供营养午餐。
何老板问同学们:“大家中午想吃什么呀?”
A同学:”牛排、意大利面、面包、水果汁”;
B同学:”酸辣粉、薯片、牛肉干、回锅肉、小虾龙、葡萄干、烤鱼、二锅头”
……
信竞班总共n个同学,每个同学都提出了自己必吃的若干种食物,何老板统计了一下,全班提出的食物总共有m种(编号1到m)。
受工资的影响,何老板最多只能提供其中k种食物,于是何老板想选出一些同学出来给他们提供午餐,要求选出来的这些同学所需的食物种类不超过k种。仁慈的何老板想让尽可能多的同学吃上午餐,问,最多能选出多少个同学出来?

输入格式

第一行,三个整数n、m和k
接下来n行,其中第i行描述第i名同学的食物需求情况,每行第1个数字t,表示该同学所需食物的种类数,接下来t个整数,表示对应食物的编号。

输出格式

一行,一个整数,表示最多能选出的同学的数量。

数据范围

对于100%的数据:
1 <=k<= m <= 15
1 <= n <= 1,000

题解:

我最开始看到数据范围就懵逼了———状压递推怎么做???
首先肯定是用食物集合状压,但是递推求集合的交集让我推了很久很久。。没做出
俗话说:敢暴力就敢A,没想到的是正解如此暴力但是却很有道理。。。
首先可以肯定的是,分发了k种食物的情况一定是最优的解。。
然后暴力枚举每位同学。。只要能满足他的需求ans++即可(每位同学的需求状压起来)
这时题目的复杂度为 O ( C m k n )

代码:

//哇,真的皮。我还以为暴力过不了呢。想了1个多小时的正解。。。。。。 
#include<iostream>
#include<cstdio>
using namespace std;
int f[1<<16],ma[1010],n,m,k;
int lowbit(int x){
    return x&(-x);
}
bool pan(int x){//判断是否刚好有k种食物 
    int tim=0;
    while(x){
        x-=lowbit(x);
        tim++;
    }
    if(tim==k)return true;
    return false; 
}
int main(){
    int i,j,p,x,maxn,ans=0;
    scanf("%d%d%d",&n,&m,&k);
    for(i=1;i<=n;i++){
        scanf("%d",&p);
        for(j=1;j<=p;j++){
            scanf("%d",&x);
            ma[i]+=1<<(x-1);//将同学的需求状压起来 
        }
    }
    maxn=(1<<m)-1;
    for(i=1;i<=maxn;i++)if(pan(i))//如果刚好有k种食品才开始计算,否则严重超时——O(2^m*n) 
        for(j=1;j<=n;j++)
            if((i&ma[j])==ma[j])f[i]++;//只要满足他就ans++ 
    for(i=1;i<=maxn;i++)if(pan(i))ans=max(ans,f[i]);
    printf("%d",ans);
    return 0;
}

D.nodgd参加APIO

题目背景:

nodgd正在APIO赛场上努力的A题。他按照以往的做法,先把所有题都看一遍,再选自己觉得最好做的题先做。nodgd觉得第二题是最容易上手的,这道题是这样描述的:一开始给你一个长度为N的非负整数序列,你需要把它砍断分成M段,那么这一分割方案的得分为……
给你这个序列以及正整数M,你需要求出最大的得分,并输出任意一个得到这个分数的方案。
   看完题,nodgd又犯了老毛病,也就是考试的时候浮想联翩。他觉得这个题还不够有趣,于是决定修改一下题目。

题目描述:

  将长度为N的非负整数序列分成M段,从左往右编号1到M,其中的编号为k的一段至少要有k个数,该段的得分为其中最大的一个数的值。总的得分就是这M个段的得分总和,请你求出最大的总分。
   nodgd觉得这是一道不错的题目,于是就丧心病狂把它拿来当成高一同学的月赛题了……考虑到是高一同学,需要以鼓励和学习为主,所以nodgd把数据范围设置得比较小,并且不需要你输出方案。

输入格式

第一行两个正整数N,M,表示序列的长度以及你需要分割成的块数。
接下来N行每行一个数,为这个序列。

输出格式

只需要输出一个数,表示你能够获得的最大得分。

题解:

一看就知道是动归题目,状态f[i][j]表示前i项分为j段后的最大得分。
首先,枚举到第i个数时,我们可以把它加入前一段中,即为f[i-1][j]
或者将它单独分出成为单独一段。这时我们可以知道:这个数足够大,并且它显然是i-j的中最大的,所以这时f为f[i-j][j-1]+max(i-j+1,i),而max可以RMQ打表预处理。
时间复杂度: O ( n l o g ( n ) + n m )

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#define LL long long
using namespace std;
int fmaxx[200010][35],f[200010][25]; 
int getmaxx(int l,int r){
    int k=floor(log2(r-l+1));
    return max(fmaxx[l][k],fmaxx[r-(1<<k)+1][k]);//O(1)计算区间最值 
}
int main(){
    int k,i,j,n,m,p;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)scanf("%d",&fmaxx[i][0]);
    k=ceil(log2(n));
    for(i=1;i<=k;i++)
        for(j=1;j<=n;j++)if((1<<i)<=n)
            fmaxx[j][i]=max(fmaxx[j][i-1],fmaxx[j+(1<<i-1)][i-1]);//RMQ预处理 
    f[1][1]=fmaxx[1][0];
    for(j=1;j<=m;j++){
        for(i=j*(j+1)/2;i<=n;i++){
            f[i][j]=max(f[i-1][j],f[i-j][j-1]+getmaxx(i-j+1,i));//动规 
        }
    }
    printf("%d",f[n][m]);
    return 0;   
}

明天再接再励!!!

猜你喜欢

转载自blog.csdn.net/qq_42013837/article/details/80600500
6.6