先发表一下感言:
在经过了一个月的“暑假”后,又迎来了期盼着的夏令营,然而我已经很久没有打过代码了!是如此悲•怆……不过不要紧,慢慢来,从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,A≠C
或者
B≠D)
,使
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
【题目分析】
就是一个直角坐标系上有很多个点(坐标为正),每两点间我们可以求出一个 曼哈顿距离 ,求是否有两个相同的距离。
曼哈顿距离:指两点间横坐标的差的绝对值与纵坐标的差的绝对值的和• |X1−X2|+|Y1−Y2|
【解题思路】
在分析完题目以后,我们首先想到的当然是
n4
的算法——枚举四个点,看看是否符合,但要注意,四个点中可以有一个重复(见样例数据1)
但这样显然会超时啊不能过数据,所以看范围我们又联想到
n2
。其实这种方法很容易想到,无非就是直接算出两两之间的距离,然后看看有没有一个距离有两个就可以了。
不过这样做可以过100%的点……
这是个神奇的事情!为什么呢?因为坐标规定了不超过
105
。我们这样想:曼哈顿距离在
105
的坐标系里,最大值是
2∗105
,也就是说一共只有这么多个空间,再多了必然会有重复!——没错就是传说中的鸽巢原理(抽屉原理)!
也就是说每组数据最多就跑这么多次啦!没错就是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)满足:
a≠b≠c≠d,1≤a<b≤n,1≤c<d≤n,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)
,完美~但我还是不会……因为计算好麻烦,还有重复。但在经过一番点拨后,我豁然开朗,发现和我想的差不多啊~就是利用求逆序对的方法求出每个数字做
a、b、c、d
的个数然后经过一系列计算得出答案。
这种计算是什么呢?就是组合数学学的容斥原理!即把所有算出来然后减去多的一部分。对于此题,我们可以求出所有顺序对和逆序对的总数,即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;
}
====================华丽丽的分割线====================
总结:感觉第一天题目不难,就是对于数据的思考与理解需要动脑,像容斥原理中【容】和【斥】哪部分都是需要考虑的,还有一些读入输出的细节也要注意。