2017.7.15 NOIP模拟

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

【试题概览】

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

1.塔

【题目描述】

给出 N 个木块,告诉你每块木块的高度,你要用这些木块搭出两座高度相同的塔,一座塔的高度为搭建它的木块的
高度和,并且一座塔至少要用一块木头。木块只能用一次,也可以不用。问在两座塔的高度相同的限制下,能够搭
的塔的最大高度是多少?

【输入文件】

第一行一个整数 N,表示木块个数;
第二行 N 个整数,表示 N 块木块的高度。

【输出文件】

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

【样例输入】

3\
2 3 5

【样例输出】

5

【数据规模】

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

题解

由于高度范围到500000\
所以显开一个dp[500000][5000000]\
描述能否达到左塔为i右塔为j的 然后扫描dp[i][i]是不行的\
所以要改变dp的方式为==差值dp==\
dp[i][j]表示

前i个木块使两个塔的高度差为j时高塔的高度的最大值

所以可以由四种状态转移而得来

1.对于木块i不放
2.放在矮的上面但是高度不超过高的
3.放在高的上面
4.放在矮的上面但是超过了高塔的高度

这样得到转移方程 dp 输出dp[n][0]就行

注意 : 因为只有dp[0][0]是合法的所以dp[0][0]=0;
其他的为-inf
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#define inf 0x7fffff
using namespace std;
int a[55];
int dp[55][500005];
int tot=0;
int main(){
//  freopen("tower.in","r",stdin);
//  freopen("tower.out","w",stdout);
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        tot+=a[i];
    }
    //printf("tot:%d\n",tot);
    for(int i=0;i<=n;i++)
        for(int j=0;j<=tot;j++)
            dp[i][j]=-inf;
    dp[0][0]=0;
    for(int i=1;i<=n;i++){
        //for(int j=0;j<=tot;j++) dp[i][j]=dp[i-1][j];
        for(int j=0;j<=tot;j++)
        {
            dp[i][j]=max(dp[i-1][j],dp[i-1][j+a[i]]);
            if(j>=a[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+a[i]);
            else dp[i][j]=max(dp[i][j],dp[i-1][a[i]-j]+j);
            //dp[i][j+a[i]]=max(dp[i-1][j+a[i]],dp[i-1][j]+a[i]);
            //if(j<=a[i]) dp[i][a[i]-j]=max(dp[i][a[i]-j],dp[i-1][j]+a[i]-j);
        //  else dp[i][j-a[i]]=max(dp[i][j-a[i]],dp[i-1][j]);
        }
    }
    if(dp[n][0]>0)
        printf("%d",dp[n][0]);
    else printf("-1");
    return 0; 
}

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\
-5 1 5 1

【样例输出 1】

0

【样例输入 2】

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

【样例输出 2】

3

【数据规模】

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

题解

找x1,y1和x2,y2分别在几个圆里面\
如果都在一个圆里面 就不算\
输出cnt1+cnt2就行了

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define X 1001
using namespace std;
int ctx1=0,ctx2=0;
int x;int y;int x2;int y2;
struct node{
    int x,y,r;
}c[55];
bool incircle(int x,int y,int a,int b,int r){
    int t1=x-a;int t2=y-b;
    if(t1*t1+t2*t2<=r*r) return 1;
    return 0;
}
bool flagx1=0;bool flagx2=0;
/*
void rep(int x,int y,int a,int b){
    if(2*y-b<=2001&&2*y-b!=b)
        mp[a][2*y-b]++;
    if(2*x-a<=2001&&2*x-a!=a)
        mp[2*x-a][b]++; 
    if(2*y-b<=2001&&2*x-a<=2001&&2*x-a!=a&&2*y-b!=b)
        mp[2*x-a][2*y-b]++;
}*/
void solve(int p){
    int tx=c[p].x,ty=c[p].y,tr=c[p].r;
    flagx1=0;flagx2=0;
    if(incircle(tx,ty,x,y,tr))  
        flagx1=1;
    if(incircle(tx,ty,x2,y2,tr))    
        flagx2=1;
    if (flagx1&&flagx2) return ;
    else{
        if(flagx1) ctx1++;
        else if(flagx2) ctx2++;
    }
    //printf("mp: %d",mp[996][1002]);
}
int main(){
    freopen("circle.in","r",stdin);
    freopen("circle.out","w",stdout);
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&c[i].x);
    for(int i=1;i<=n;i++)
        scanf("%d",&c[i].y);
    for(int i=1;i<=n;i++)
        scanf("%d",&c[i].r);
    for(int i=1;i<=n;i++)
    {
        c[i].x+=X;c[i].y+=X;
    }
    scanf("%d%d%d%d",&x,&y,&x2,&y2);
    x+=X;x2+=X;y+=X;y2+=X;
    if(x==x2&&y==y2)
    {
        printf("0");
        return  0;
    }
    for(int i=1;i<=n;i++)
        solve(i);
    //printf("%d %d",ctx1,ctx2);
    printf("%d",ctx1+ctx2);
    return 0;
}

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。

【样例输入】

3 2\
-1 3\
3 -1\
1 2\
1 2\
3 1

【样例输出】

-1\
1\
1

【数据规模】

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

题解

比较难的一题\
要反过来思考\
首先求出m-1秒后猴子的情况\

然后反过来 ==把从猴子上掉下来看为从地上接上去==
那么如果接到了一个有1的连通块
那么就是这个时候从1这个连通块上面掉下来的

这样 此题就变成了一个简单的并查集的题目\
每次用并查集维护连通块 记录时间即可

关于为什么要重新在输出的时候findf一遍的原因
因为在之前的findf只是把要松手的猴子findf一遍
比如说
5 2
2 -1
3 -1
4 -1
5 -1
-1 -1
1 1
4 5

这组数据就是一条链\
然后1 2放手 4 5放手\
把3 这个点夹在了中间

但是没有最后一遍的findf 3的时间就是找不到的
所以要多findf一遍
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
int a[2000005][3];
int fa[2000005],time[2000005];
bool mk[2000005][3];
struct node{
    int x,y;
}e[400005];
int findf(int x){
    if(fa[x]==x) return x;
    else{
        int tmp=fa[x];
        fa[x]=findf(fa[x]);
        if(time[tmp]<time[x]) time[x]=time[tmp];
        return fa[x];
    }
}
void merge(int x,int y,int t){
    if(x==1)
    {
        fa[y]=x;
        time[y]=t;
    } 
    else if(y==1){
        fa[x]=y;
        time[x]=t;
    }
    else fa[x]=y;
}
int main(){ 
    freopen("monkey.in","r",stdin);
    freopen("money.out","w",stdout);
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&a[i][1],&a[i][2]);
    int x,y;
    for(int i=0;i<=m-1;i++){
        scanf("%d%d",&x,&y);
        mk[x][y]=1;
        e[i].x=x;e[i].y=y;
    }
    memset(time,127,sizeof(time));
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=2;j++){
            if(!mk[i][j]&&a[i][j]!=-1)
            {
                int x=findf(i),y=findf(a[i][j]);
                if(x!=y) merge(x,y,m);
            }
        }//先处理m-1秒之后的情况 没有断的先连在一起 
    for(int i=m-1;i>=0;i--){
        if(a[e[i].x][e[i].y]!=-1){
            int x=findf(e[i].x);
            int y=findf(a[e[i].x][e[i].y]);
            if(x!=y) merge(x,y,i);
        }
    }
    for(int i=1;i<=n;i++)
    {
        findf(i);//重新dfs 很关键
        if(time[i]>=m) printf("-1\n");
        else printf("%d\n",time[i]);
    }
    return 0; 
}

4.山

【题目描述】

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

【输入文件】

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

【输出文件】

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

【样例输入】

6\
0 0\
10 0\
11 1\
15 1\
16 0\
25 0\

【样例输出】

3.00

【数据规模】

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

题解

对于每个直线 算出解析式
因为要求的是高度 那么二分一个高度值
对于一个直线

    f(x)=kx+b

那么这个答案点是要满足在所有直线所夹之间的(平行的线就在上方)

可以二分答案

所以把每个二分的答案

代入算一个x的取值范围:从l到r\
如果满足l

判断直线的k值
如果k>0 说明可以更新一个有端点
k==0 就判断y值和y=kx+b中的b 如果b大于y 则不合法
k<0 同理更新左端点
那么y可以向小的尝试(r=mid)\
否则(l=mid)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define eps 1e-8
#define inf 0x7ffffff
using namespace std;
struct node{
    double k,b;
}line[15005];
int n;
double x[15005],y[15005];
bool check(double y){
    double l=-inf,r=inf;
    for(int i=1;i<=n-1;i++){
        if(line[i].k>0){
            double xx=(y-line[i].b)/line[i].k;
            r=min(xx,r);
        }
        if(line[i].k==0&&line[i].b>y) return false;
        if(line[i].k<0){
            double xx=(y-line[i].b)/line[i].k;
            l=max(xx,l);
        }
        if(l>r) return false;
    } 
    return true;
}
int main(){
    //freopen("hill.in","r",stdin);
    //freopen("hill.out","w",stdout);
    scanf("%d",&n);
    double l=0,r=1000000;
    double ans=0;
    scanf("%lf%lf",&x[1],&y[1]);
    for(int i=2;i<=n;i++){
        scanf("%lf%lf",&x[i],&y[i]);
        double disy=(y[i]-y[i-1]);double disx=(x[i]-x[i-1]);
            double k=disy/disx;
            double b=y[i]-k*x[i];
            line[i-1].k=k;line[i-1].b=b;
    }
    while(l+0.001<r){
        double mid=(l+r)/2;
        if(check(mid)){
            ans=r=mid;
        }
        else l=mid;
    }
    printf("%lf",ans);
    return 0;   
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值