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

先发表一下感言:

在经过了一个月的“暑假”后,又迎来了期盼着的夏令营,然而我已经很久没有打过代码了!是如此悲•怆……不过不要紧,慢慢来,从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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值