【解题报告】2016.8.4·OI夏令营·开营测试

来源:互联网 发布:电脑自动按键软件 编辑:程序博客网 时间:2024/06/05 07:58

先发表一下感言:

在经过了一个月的“暑假”后,又迎来了期盼着的夏令营,然而我已经很久没有打过代码了!是如此悲•怆……不过不要紧,慢慢来,从Day1开始。

今天做了三题,总体难度不大,都是学过的内容,只是某些细节要做好处理。

那么我们就要开始讲题目了!

====================华丽丽的分割线====================

T1•音阶(ljestvica/1S/64M)
【题目描述】
Veronica进入了音乐学院。她收到了一张只有音符没有注释的乐谱,需要认出乐谱中用到的音阶。在本题中,我们只用到了两种最常用的(而且也是学校最先教的)音阶:A小调和C大调。这并不是说这两个音阶比其他大调、小调更简单或基础, 所有的小调和大调都是差不多的。

现代音乐中一个八度有12个音(A, A#, B, C, C#, D, D#, E ,F, F#, G, G#),A小调和C大调也是用这12个音组成。A小调是一组有序的七个音{A,B,C,D,E,F,G},C大调是{C,D,E,F,G,A,B}。

注意,这两个音阶用到的音是一样的。那区别在哪?确定一个音阶,重点不仅在用到了什么音,还有他们的用法。主音(一个音阶的第一个音), 下属音(第四个音),属音(第五个音)在一个音阶中是重音的首选。在A小调中就是A、D、E,在C大调中就是C、F、G。我们把这些音叫main tones。

大调和小调有什么不同呢?比方说,A小调的中音(第三个音)比主音高三个半音,C大调的中音比主音高四个半音。总之,差别就在于两个相邻的音的距离。这使小调听起来伤感,大调听起来喜庆。

现在你要写一个程序判断这首曲子是用A小调写的还是用C大调写的。可以数在重音(每小节的第一个音)中是A小调的main tones多还是C大调的main tones多。如果main tones数相同,若最后一个音是A小调的main tones,这首曲子就是A小调,否则就是C大调。

比如说,现在来判断著名的旋律“你在睡觉吗?”;
CD|EC|CD|EC|EF|G|EF|G|GAGF|EC|GAGF|EC|CG|C|CG|C
字符“|”把每个小节隔开了,所以这个旋律的重音依次是:C,E,C,E,E,G,E,G,G,E,G,E,C,C,C,C。有10个C大调的main tones,6个A小调的main tones,所以这个旋律是C大调的。

【输入格式】
输入文件仅一行, 包含一个序列(最短为5, 最长100), 每个字母都包含在{“A”, “B”, “C”, “D”, “E”, “F”, “G”, “|”} 中。 其中”|” 将每小节分开, 且不会出现在序列的开头或结尾。

【输出格式】
输出文件仅一行,为” C-dur “ (C大调) 或 “ A-mol” (A小调)。

【输入1】
AEB|C
【输出1】
C-dur
【输入2】
CD|EC|CD|EC|EF|G|EF|G|GAGF|EC|GAGF|EC|CG|C|CG|C
【输出2】
C-dur

【题目分析】
求A调C调分别的重音个数,比较大小判断属于哪个调。

【解题思路】
这道题关键在于是看好题目:第一是“重音”的理解,第二是如果两个调重音个数相同的判别,第三是注意开头第一个重音要记得加上……然后就没有然后了,直接for一遍做就可以了。

【AC程序】

#include<iostream>#include<cstdio>#include<cstring>using namespace std;char st[102];int Cdur,Amol,len;int main(){    freopen("ljestvica.in","r",stdin);    freopen("ljestvica.out","w",stdout);    Cdur=Amol=0;    scanf("%s",st);    len=strlen(st);    if(st[0]=='A'||st[0]=='D'||st[0]=='E')            ++Amol;        else        if(st[0]!='B')            ++Cdur;    for(int i=0;i<len;++i)    {        if(st[i]=='|')            if(st[i+1]=='A'||st[i+1]=='D'||st[i+1]=='E')                ++Amol;            else            if(st[i+1]!='B')                ++Cdur;    }    if(Amol==Cdur)        if(st[len-1]=='A'||st[len-1]=='D'||st[len-1]=='E')            printf("A-mol\n");        else            printf("C-dur\n");    else        if(Amol>Cdur)            printf("A-mol\n");        else            printf("C-dur\n");    return 0;}

写得有点长不过可以过就行啦~~~

====================华丽丽的分割线====================

T2波老师(teacher/1S/64M)
【题目描述】
波波老师是一个地理老师。有一天他上课的时候,他在地图上标记了N个点,第i个点在点(Xi,Yi)。他想知道,是否存在四个点(A,B,C,D)(A<B,C<D,AC或者BD),使AB之间的曼哈顿距离和CD之间的曼哈顿距离相等。
如果存在这样的四个点,输出YES,否则输出NO。

【输入格式】
输入文件第一行是一个T(T≤50),表示有T组数据。
接下来有T组数据,每组数据第一行是两个整数N,M,表示点的个数以及点的坐标的边界,然后有N行,第i行有两个整数Xi,Yi表示第i个点的坐标(Xi,Yi)(0≤Xi,Yi≤M)

【输出格式】
输出文件有T行,每一行为YES或者NO。

【输入】
2
3 10
1 1
2 2
3 3
4 10
8 8
2 3
3 3
4 4

【输出】
YES
NO

【数据范围】
80% n≤1000,m≤1000
100% n≤105,m≤105

【题目分析】
就是一个直角坐标系上有很多个点(坐标为正),每两点间我们可以求出一个 曼哈顿距离 ,求是否有两个相同的距离。

曼哈顿距离:指两点间横坐标的差的绝对值与纵坐标的差的绝对值的和•|X1X2|+|Y1Y2|

【解题思路】
在分析完题目以后,我们首先想到的当然是n4的算法——枚举四个点,看看是否符合,但要注意,四个点中可以有一个重复(见样例数据1)
但这样显然会超时啊不能过数据,所以看范围我们又联想到n2。其实这种方法很容易想到,无非就是直接算出两两之间的距离,然后看看有没有一个距离有两个就可以了。
不过这样做可以过100%的点……
这是个神奇的事情!为什么呢?因为坐标规定了不超过105。我们这样想:曼哈顿距离在105的坐标系里,最大值是2105,也就是说一共只有这么多个空间,再多了必然会有重复!——没错就是传说中的鸽巢原理(抽屉原理)!
也就是说每组数据最多就跑这么多次啦!没错就是break啦~

【AC代码】

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>using namespace std;struct Tp{    int x,y;};Tp ps[100002];int t,n,m;short bo[200010];int main(){    freopen("teacher.in","r",stdin);    freopen("teacher.out","w",stdout);    scanf("%d",&t);    while(t--)    {        memset(bo,0,sizeof(bo));        scanf("%d%d",&n,&m);        bool flag=false;        for(int i=0;i<n;++i)        {            scanf("%d%d",&ps[i].x,&ps[i].y);            for(int j=0;j<i;++j)            {                int tmp=abs(ps[i].x-ps[j].x)+abs(ps[i].y-ps[j].y);                bo[tmp]++;                if(bo[tmp]>1)                {                    flag=true;                    break;                }            }            if(flag)            {                for(int j=i+1;j<n;++j)                    scanf("%d%d",&ps[i].x,&ps[i].y);                break;            }        }        if(flag)            printf("YES\n");        else            printf("NO\n");    }    return 0;}

这里有个点要注意,就是break以后你要把剩下的数据也读进来……不然会像我一样boom~~

====================华丽丽的分割线====================

T3爆裂吧世界(world/1S/64M)

【题目描述】
给你一个长度为n的数列A,请你计算里面有多少个四元组(a,b,c,d)满足:
abcd,1a<bn,1c<dn,Aa<Ab,Ac>Ad

【输入格式】
输入文件第一行有一个整数N,第二行有N个整数A1,A2⋯An

【输出格式】
输出文件仅一行,为一个整数,表示满足条件的四元组的数量

【输入1】
4
2 4 1 3
【输出1】
1
【输入2】
4
1 2 3 4
【输出2】
0
【数据约定】
15% n<=100
100%n<=50000
A在int范围里

【题目分析】
这题,字面意思,就是字面意思,真的是字面意思。
重要的事情说三遍,题目符号真的要看好。

【解题思路】
首先我们还是可以暴力的,不过只有15%,具体你懂的。
然后直接就要考虑这么大,我好方……考试的时候还有十来分钟想到了树状数组,但是并没有时间做了啊,打暴力咯~
为什么会想到树状数组呢?因为里面的ab和cd都是有大小关系的,自然而然想到了什么逆序对顺序对,O(nlog2n),完美~但我还是不会……因为计算好麻烦,还有重复。但在经过一番点拨后,我豁然开朗,发现和我想的差不多啊~就是利用求逆序对的方法求出每个数字做abcd的个数然后经过一系列计算得出答案。
这种计算是什么呢?就是组合数学学的容斥原理!即把所有算出来然后减去多的一部分。对于此题,我们可以求出所有顺序对和逆序对的总数,即sumab和sumcd,然后减去a和c是一个数、a和d是一个、b和c是一个数以及b和d是一个数的情况就可以了。
具体做法就是用树状数组,先离散化,然后sort,打标号,算出能做b有多少个,做c有多少个,再算出能做a、d有多少个就行,自行理解。

【AC程序】

#include<iostream>#include<cstdio>#include<algorithm>#include<cstring>using namespace std;const int N=50005;struct Tnode{    int num;    int pos;};Tnode node[N];int tree[N],reflect[N],n;int small[N],little[N],big[N],large[N];long long ans,sumab,sumcd;int cmp(Tnode a,Tnode b){    return a.num<b.num;}int lowbit(int x){    return x&(-x);}void update(int x){    while(x<=n)    {        tree[x]++;        x+=lowbit(x);    }}int getsum(int x){    int sum=0;    while(x>0)    {        sum+=tree[x];        x-=lowbit(x);    }    return sum;}int main(){    freopen("world.in","r",stdin);    freopen("world.out","w",stdout);    scanf("%d",&n);    for(int i=1;i<=n;++i)    {        scanf("%d",&node[i].num);        node[i].pos=i;    }    sort(node+1,node+n+1,cmp);    int tmp=0;    for(int i=1;i<=n;++i)    {        if(i==1 || node[i].num!=node[i-1].num)            ++tmp;        reflect[node[i].pos]=tmp;    }    memset(tree,0,sizeof(tree));    for(int i=1;i<=n;++i)    {        small[i]=getsum(reflect[i]-1);        big[i]=i-1-getsum(reflect[i]);        update(reflect[i]);        sumab+=small[i];        sumcd+=big[i];//printf("%d\n",i-getsum(reflect[i]));    }    memset(tree,0,sizeof(tree));    for(int i=n;i>=1;--i)    {        little[i]=getsum(reflect[i]-1);        large[i]=n-i-getsum(reflect[i]);        update(reflect[i]);//printf("%d\n",i-getsum(reflect[i]));    }    ans=(long long)sumab*sumcd;    for(int i=1;i<=n;++i)    {        ans-=(long long)small[i]*little[i];        ans-=(long long)small[i]*big[i];        ans-=(long long)big[i]*large[i];        ans-=(long long)little[i]*large[i];    }//cout<<ans<<endl;    printf("%I64d\n",ans);    return 0;}

====================华丽丽的分割线====================

总结:感觉第一天题目不难,就是对于数据的思考与理解需要动脑,像容斥原理中【容】和【斥】哪部分都是需要考虑的,还有一些读入输出的细节也要注意。

0 0
原创粉丝点击