11.14NOIP考前模拟赛(中位数、敲砖块)

来源:互联网 发布:汉字意义 知乎 编辑:程序博客网 时间:2024/06/08 06:10

一、敲砖块
【题目描述】
在一个凹槽中放置了N层砖块,最上面的一层油N块砖,从上到下每层一次减少一块砖。每块砖都有一个分支,敲掉这块砖就能得到相应的分值,如图所示。

如果你想敲掉第i层的第j块砖的话,若i=1,你可以直接敲掉它;若i>1,则你必须先敲掉第i-1层的第j和第j+1块砖。
你现在可以敲掉最多M块砖,求得分最多能有多少。
【输入文件】
输入文件的第一行有两个正整数N和M;
接下来的N行,描述这N层砖块上的分值A[i,j],满足0<=A[i,j]<=100。
【输出文件】
仅一行,包含一个整数,为最大的得分。
【样例输入】
4 5
2 2 3 4
8 2 7
2 3
49
【样例输出】
19
【数据规模】
对于20%的数据,满足1<=N<=10,1<=M<=30
对于100%的数据,满足1<=N<=50,1<=M<=500。

题解:
首先我们将砖块向左对齐,变成一个直角三角形的形式,可以发现:
1. 每一列必须敲掉从第一行开始的从上到下的若干个砖块
2.如果某一列敲掉了K个砖块,那么其右边的那一列至少敲掉了K-1个砖块。
所以我们可以dp。
f[i][j][k]表示从右到左已经敲到了第I列,其中第I列敲掉了J个砖块且还剩K个砖块所得到的最大价值。
f[i][j][k]=max(f[i+1][v][k+j])+a[1][i]+..+a[1]=j-1”>j

#include<cstdio>#include<cstring>#include<iostream>using namespace std;int n,m,a[510][510],fi,fj,fk,v[510][510],f[60][60][510],ans; int main(){    freopen("brike.in","r",stdin);    freopen("brike.out","w",stdout);    scanf("%d%d",&n,&m);    for (int i=1;i<=n;i++)      for (int j=1;j<=n-i+1;j++)        {            scanf("%d",&a[i][j]);        }    for (int i=n;i>=1;i--)      for (int j=1;j<=n-i+1;j++)         v[i][j]=v[i][j-1]+a[j][i];    memset(f,-1,sizeof(f));    f[n+1][0][m]=0;    for (int j=n;j>=1;j--)      for (int i=0;i<=n-j+1;i++)        for (int k=m-i;k>=0;k--)        {            for (int l=i-1;l<=n-j+1;l++)            if (f[j+1][l][k+i]!=-1)                f[j][i][k]=max(f[j][i][k],f[j+1][l][k+i]+v[j][i]);            if (ans<f[j][i][k])            {                ans=f[j][i][k];            }        }      printf("%d",ans); }

二、中位数

【题目描述】
有一个长度为N的数列{A1,A2,…An},这N和数字恰好是1..N的一个排列。你需要统计有多少个子序列{Ai,Ai+1,…Aj}满足:i<=j且j-i+1为奇数,序列的中位数为B。例如{5,1,3}的中位数为3。
【输入文件】
第一行包含两个正整数N和B;第二行包含N个整数,第i个整数为Ai。
【输出文件】
仅包含一个整数,为满足条件的子序列的个数。
【样例输入】
7 4
5 7 2 4 3 1 6
【样例输出】
4
【数据规模】
对于30%的数据中,满足N<=100;
对于60%的数据中,满足N<=1000;
对于100%的数据中,满足N<=100000,1<=B<=N。

这道题是一道比较好想的递推,给@http://blog.csdn.net/zhhe0101同学讲了一下午,这里再写一下自己的思路。
关键点就在于,如果有中位数k,那么一定有n个比k大的数和n个比k小的数,又由于数与数之间必须是挨着的,所以我们维护一个类似于前缀和的东西:
1.记录k的位置
2.先从k+1到末尾扫一遍(i++),如果当前数值大于k,那么f[i]=f[i-1]+1;如果小于就f[i]=f[i-1]-1;
3.看f[i]的值,如果等于0,ans++。
否则p[f[i]]++;(接下来会用到)
4.从k-1到1扫一遍。运用和上一次相似的方法,不过变成f[i]=f[i+1]+1和f[i]=f[i+1]-1了。
5.看f[i]的值,如果等于0,ans++。
否则ans+=p[f[i]的相反数](因为是相反数,所以代表大于k的数和小于k的数的数目是相同的)
6.printf
PS不过要注意,c++语言是没有负数下标的,所以要把100000看做0下标,即小于100000的数相当于小于零(为什么要100000,因为n最大是100000啊)。

#include<cstdio>#define s 100000#define NUM 100010using namespace std;int n,m,ans,j,a[NUM],f[NUM],p[NUM*10];int main(){    freopen("median.in","r",stdin);    freopen("median.out","w",stdout);    scanf("%d%d",&n,&m);    for (int i=1;i<=n;i++)    {        scanf("%d",&a[i]);        if (a[i]==m)          j=i;    }    f[j]=s;    for (int i=j+1;i<=n;i++)    {        if (a[i]>m)        {          f[i]=f[i-1]+1;        }        else        {          f[i]=f[i-1]-1;        }          p[f[i]]++;        if (f[i]==s)          ans++;    }    for (int i=j-1;i>=1;i--)    {        if (a[i]>m)        {          f[i]=f[i+1]+1;        }        else        {          f[i]=f[i+1]-1;        }          ans+=p[s-f[i]+s];        if (f[i]==s)          ans++;    }    printf("%d",ans+1);}
1 0
原创粉丝点击