2017.7.15 NOIP模拟

来源:互联网 发布:法棍 知乎 编辑:程序博客网 时间:2024/06/06 22:45

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

【试题概览】

试题名称 塔 圆 猴子 山 提交文件 tower circle monkey hill 输入文件 tower.in circle.in monkey.in hill.in 输出文件 tower.out circle.out monkey.out hill.out 时间限制 1s 1s 1s 1s 空间限制 128MB 128MB 128MB 128MB 题目来源 Topcoder Topcoder POI X COI2004

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 0x7fffffusing 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 1001using 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 22 -13 -14 -15 -1-1 -11 14 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 0x7ffffffusing 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;   }