[noip模拟赛2017.7.15]

全国信息学分区联赛模拟试题(七)

【试题概览】

试题名称猴子
提交文件towercirclemonkeyhill
输入文件tower.incircle.inmonkey.in hill.in
输出文件tower.outcircle.outmonkey.outhill.out
时间限制1s1s1s1s
空间限制128MB128MB128MB128MB
题目来源TopcoderTopcoderPOIXCOI

1、塔

【题目描述】

给出 N 个木块,告诉你每块木块的高度,你要用这些木块搭出两座高度相同的塔,一座塔的高度为搭建它的木块的 高度和,并且一座塔至少要用一块木头。木块只能用一次,也可以不用。问在两座塔的高度相同的限制下,能够搭 的塔的最大高度是多少? 【输入文件】 第一行一个整数 N,表示木块个数; 第二行 N 个整数,表示 N 块木块的高度。

【输出文件】

仅一个数,表示能搭建的最高的塔的高度,若不不能搭建两座相同高度的塔,输出-1。

【样例输入】

3 235

【样例输出】

5

【数据规模】

N<=50,每块木块的高度范围[1,500000],所有木块的高度总和<=500000。

Solution

  1. 第一题是一个典型的差值dp,至于为什么我当时想了那么久,因为是真的没有碰过这类型的题目。我们大概了解了题目意思之后,可以清楚,对于每一个积木有三种选择:1.不放. 2.放在较高的那座塔. 3.放在较低的那座塔. 至于为什么是较低和较高不是第一座和第二座,有两个原因。一.都说了是差值dp,肯定是有比较的两个,而比较的依据不用说,一定是高度。二.如果仅定义第一座塔比第二座塔高多少,有负数状态。网上有很多题解都说什么是第一座塔,感觉不合适。那么我们很清楚,f[i][j]表示考虑到第i个积木,较高塔比较低塔高j时,较高塔的高度。那么对应三种状态的转移可列出这么几个式子

a) f[i][j]=f[i-1][j] 不选择; b) f[i][j]=f[i-1][j-a[i]]+a[i] 放在较高塔那么,高度增加; c) f[i][j]=f[i-1][a[i]-j]+j 放在低塔且高度超过; d) f[i][j]=f[i-1[i+a[i] 高度没有超过;

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>

using namespace std;

int n,a[55],tot=0,ans,sum[55];
int f[55][500050];

int main()
{   
    freopen("tower.in","r",stdin);
    freopen("tower.out","w",stdout);
    scanf("%d",&n);
    memset(f,-63,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]),tot+=a[i];
    for(int i=1;i<=n;i++)
        for(int j=tot;j>=0;j--)
        {
            f[i][j]=max(f[i][j],f[i-1][j]);
            if(j>=a[i])
                f[i][j]=max(f[i][j],f[i-1][j-a[i]]+a[i]);
            if(j<a[i])
                f[i][j]=max(f[i][j],f[i-1][a[i]-j]+j); 
            f[i][j]=max(f[i][j],f[i-1][j+a[i]]); 
        }
    if(f[n][0])printf("%d",f[n][0]);
    else printf("-1");
    return 0;
}
/*
10
11 12 13 14 15 16 17 18 19 20
*/

2、圆

【题目描述】

给出 N 个圆,保证任意两个圆都是相离的,然后给出两个点(x1,y1)、(x2,y2),保证均不在某个圆上,要从(x1,y1)到 (x2,y2)画条曲线,问这条曲线最少要穿过多少次圆的边界?

【输入文件】

第一行一个整数 N,表示圆的个数; 第二行 N 个整数,表示 N 个圆的 X 坐标; 第三行 N 个整数,表示 N 个圆的 Y 坐标; 第四行 N 个整数,表示 N 个圆的半径 R; 第五行四个整数 x1,y1,x2,y2。

【输出文件】

仅一个数,表示最少要穿过多少次圆的边界。

【样例输入 1】

1 0 0 2 -5151

【样例输出 1】

0

【样例输入 2】

7 1-325-41212 1-125511 8121112 -51121

【样例输出 2】

3

【数据规模】

1<=N<=50,坐标范围[-1000,1000],每个圆的半径 1<=R<=1000。 保证没有两个圆有公共点,起点和终点不会落在某个圆的边界上。

Solution

第二题比较水,就是判断两个圆是否在同一个圆里,复杂度O(n).

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;

int n,ans,x[55],y[55],r[55];

struct node{
    int x,y;
}p[3];

bool judge(int c,int xx,int yy){
    if((xx-x[c])*(xx-x[c])+(yy-y[c])*(yy-y[c])<r[c]*r[c])return true;
    else return false; 
}

int main()
{
    freopen("circle.in","r",stdin);
    freopen("circle.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&x[i]);
    for(int i=1;i<=n;i++)scanf("%d",&y[i]);
    for(int i=1;i<=n;i++)scanf("%d",&r[i]);
    for(int i=1;i<=2;i++)scanf("%d%d",&p[i].x,&p[i].y);
    for(int i=1;i<=n;i++){
        bool ok_1=judge(i,p[1].x,p[1].y);
        bool ok_2=judge(i,p[2].x,p[2].y);
        if(ok_1&&ok_2)continue;
        if(!ok_1&&!ok_2)continue;
        ans++;
    }
    printf("%d",ans);
    return 0;
}
/*
7
1 -3 2 5 -4 12 12
1 -1 2 5 5 1 1
8 1 2 1 1 1 2
-5 1 12 1 
*/

3、猴子

【题目描述】

有 N 只猴子,第一只尾巴挂在树上,剩下的 N-1 只,要么被其它的猴子抓住,要么抓住了其它的猴子,要么两者均 有。当然一只猴子最多抓两只另外的猴子,只有两只手嘛。现在给出这 N 只猴子抓与被抓的信息,并且在某个时刻 可能某只猴子会放掉左手或右手的猴子,导致某些猴子落在地上。求每只猴子落地的时间。

【输入文件】

第一行两个数 N、M,表示有 N 只猴子,并且总时间为 M-1。 接下来 N 行,描述了每只猴子的信息,每行两个数,分别表示这只猴子左手和右手抓的猴子的编号,如果是-1,表 示该猴子那只手没抓其它的猴子。再接下来 M 行,按时间顺序给出了一些猴子放手的信息,第 1+N+i 行表示第 i-1 时刻某只猴子的放手信息,信息以两个数给出,前者表示放手的猴子的编号,后者表示其放的哪只手,1 表示左手, 2 表示右手。

【输出文件】

共 N 行,第 i 行表示第 i 只猴子掉落的时刻,若第 i 只猴子到 M-1 时刻以后还没掉落,就输出-1。

【样例输入】

32 -13 3-1 12 12 31

【样例输出】

-1 1 1

【数据规模】

30%的数据,N<=1000,M<=1000; 100%的数据,1<=N<=200000,1<=M<=400000。

Solution

第三题是一个好题,大意就是点何时与连通块不相连。正着处理显然会超时,那么考虑倒着思考,如果一个猴子往上跳那么他什么时候与连通块相连就是他恰好脱离的时候,可以用并查集维护,但同时也要维护一个时间t。听学长说这是一种常用的方法,然而我又不知道真的cai,不过好在考试的时候,暴力乱加优化糊了个70;自己的代码好像找不到了,这里就贴同学的代码吧%%%比我强很多的

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
#define INF (1<<30)
using namespace std;

int n,m;
struct Edge{
    int a,b;
}e[1000000];
int ch[200200][2];

int fa[200200];
void pre(){for(int i=1;i<=n;i++)fa[i]=i;}
int findf(int x){return fa[x]==x?x:fa[x]=findf(fa[x]);}
void mergef(int x,int y){fa[findf(x)]=findf(y);}

int to[1000000],nt[1000000],fp[200200],cnt=1;
void add(int x,int y){
    to[cnt]=y;nt[cnt]=fp[x];
    fp[x]=cnt++;
}
int ans[200200];
void dfs(int k,int t){
    ans[k]=t;
    for(int i=fp[k];i;i=nt[i]){
        int kk=to[i];
        if(ans[kk]!=-1)continue;
        dfs(kk,t);
    }
}
int main(){
    //freopen("monkey.in","r",stdin);
    //freopen("monkey.out","w",stdout);
    memset(ans,-1,sizeof(ans));
    scanf("%d%d",&n,&m);pre();
    for(int i=1;i<=n;i++)
        scanf("%d%d",&ch[i][0],&ch[i][1]);
    for(int i=1;i<=m;i++){
        int p,c;
        scanf("%d%d",&p,&c);c--;
        if(ch[p][c]==-1)continue;
        e[i].a=p;e[i].b=ch[p][c];   
        ch[p][c]=-1;
    }
    for(int i=1;i<=n;i++){
        if(ch[i][0]!=-1){
            if(findf(i)==findf(ch[i][0]))continue;
            mergef(i,ch[i][0]);
            add(i,ch[i][0]);
            add(ch[i][0],i);
        }
        if(ch[i][1]!=-1){
            if(findf(i)==findf(ch[i][1]))continue;
            mergef(i,ch[i][1]);
            add(i,ch[i][1]);
            add(ch[i][1],i);
        }   
    }
    for(int i=m;i>=1;i--){
        int a=e[i].a,b=e[i].b;
        if(a==0&&b==0)continue;
        int Fa=findf(a),Fb=findf(b);
        int root=findf(1);
        if(Fa==Fb)continue;
        if(Fa==root){
            dfs(Fb,i-1);
            mergef(a,b);
            add(a,b);
            add(b,a);
        }
        else if(Fb==root){
            dfs(Fa,i-1);
            mergef(a,b);
            add(a,b);
            add(b,a);
        }
        else {
            mergef(a,b);
            add(a,b);
            add(b,a);
        }
    }
    for(int i=1;i<=n;i++)   
        printf("%d\n",ans[i]);
} 

4、山

【题目描述】

给一座山,如图所示
现在要在山上的某个部位装一盏灯,使得这座山的任何一个部位都能够被看到。给出最小的 y 坐标,如图的+号处 就是 y 坐标最小的安装灯的地方。

【输入文件】

第一行一个数 N,表示这座山有 N 个点构成,接下来 N 行从左到右给出了这座山的构造情况,每行两个数 Xi,Yi, 表示一个折点,保证 Xi>Xi-1(1<i<=N)。

【输出文件】

仅输出一行,为最小的 y 坐标,当你的答案与标准答案相差 0.01 时,则被认为是正确的。

【样例输入】

6 00 100 111 151 160 250

【样例输出】

3.00

【数据规模】

30%的数据,1<=N<=50; 100%的数据,1<=N<=5000,0<=Xi,Yi<=100000,保证答案不超过 1000000。

Soluton

第四题也是个好题,乍一看还挺复杂,其实就是一个二分,由于不是封闭图,越往上肯定被看见的机会就越大,所以,相当于二分一个y值,根据线性规划,对所有的直线(看成是直线更好处理)求一个合法区间,然后看这些区间有没有交集。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
#define INF 1e15
using namespace std;

int n;
double x[5500],y[5500]; 

struct node{
    double k,b;
    node(double kk=0,double bb=0){k=kk,b=bb;}
}line[5500];

bool check(double yy){
    double ll=-INF,rr=INF;
    for(int i=1;i<n;i++)
    {
        if(line[i].k>0)
        {
            double xx=(yy-line[i].b)/line[i].k;
            rr=min(rr,xx);
        }
        if(line[i].k==0&&yy<line[i].b)return false;
        if(line[i].k<0)
        {
            double xx=(yy-line[i].b)/line[i].k;
            ll=max(ll,xx);
        }
        if(ll>rr)return false;
    }
    return true;
}

int main()
{
    freopen("hill.in","r",stdin);
    freopen("hill.out","w",stdout);
    scanf("%d",&n);
    scanf("%lf%lf",&x[1],&y[1]);
    double l=0,r=1000010,ans;
    for(int i=2;i<=n;i++)
    {
        scanf("%lf%lf",&x[i],&y[i]);
        double x_div=abs(x[i]-x[i-1]);
        double y_div=abs(y[i]-y[i-1]);
        double k=(y_div/x_div);
        if(y[i]<y[i-1])k=-k;
        double b=y[i]-k*x[i];
        line[i-1]=node(k,b);    
    }
    while(l+0.001<r){
        double mid=(l+r)/2;
        if(check(mid)){ans=r=mid;}
        else l=mid;
    }   
    printf("%.2lf",ans);
    return 0;
}  
/*
6
0 0
10 0
11 1
15 1
16 0
25 0
*/                                                        

转载于:https://www.cnblogs.com/DexterYsw/p/7201282.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值