信息学奥赛一本通(C++版) 第二部分 基础算法 第二章 数据排序
http://ybt.ssoier.cn:8088/index.php
1311 【例2.5】求逆序对 此题不适合 初学者 做 2018-5-17
//ybt 1310 【例2.2】车厢重组
//洛谷 P1116 车厢重组
//题目若不标难度,看起来很吓人,排序的算法很多,会造成比较次数不同
//归并排序 需要空间,从题意看提供不了
//该题只给了一个交换空间,基本上属冒泡或快排
//极端情况,快排与冒泡算法复杂度相同
//并且交换的元素是相邻的,故只有冒泡系列了,该题,难在思路,写写很快.
//2017-10-28 9:12 AC
#include <stdio.h>
int a[10100];
int main(){
int n,i,j,t,cnt=0;
scanf("%d",&n);
for(i=1;i<=n;i++)scanf("%d",&a[i]);
for(i=1;i<=n;i++)
for(j=i+1;j<=n;j++)
if(a[i]>a[j])t=a[i],a[i]=a[j],a[j]=t,cnt++;
printf("%d",cnt);
return 0;
}
//1311 【例2.5】求逆序对
//按冒泡思路编写,猜想,要超时,试试,看看数据水不水
//提交,果然超时,
//翻了之前的记录,发现是洛谷 P1908 逆序对 不过当时是用树状数组
//http://blog.csdn.net/mrcrack/article/details/61625530
//这次准备用归并排序做
//http://blog.csdn.net/yuehailin/article/details/68961304代码写得很对胃
//http://www.cnblogs.com/chengxiao/p/6194356.html用图分析归并排序算法过程,很清晰
//http://blog.csdn.net/acdreamers/article/details/16849761用归并排序求逆序对,关键点讲得好,摘抄如下:
//归并排序是将数列a[l,h]分成两半a[l,mid]和a[mid+1,h]分别进行归并排序,然后再将这两半合并起来。
//在合并的过程中(设l<=i<=mid,mid+1<=j<=h),当a[i]<=a[j]时,并不产生逆序数;当a[i]>a[j]时,在
//前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i。因此,可以在归并
//排序中的合并过程中计算逆序数.
//样例通过,提交,未通过,
//同样的程序在洛谷里提交,AC,真是太奇葩了,洛谷 P1908 逆序对
//https://www.luogu.org/problemnew/show/P1908
//转念一想,可能是逆序对太多,int溢出,把int 改成long long 修改,提交AC 真不容易啊2017-10-28 22:26
//将int 改成 long long 整整用了17分钟
#include <stdio.h>
int a[100100],b[100100];
long long ans=0;////转念一想,可能是逆序对太多,int溢出,把int 改成long long
void memery_sort(int left,int mid,int right){//自小到大
int i=left,j=mid+1,n=mid,m=right,k=0;
while(i<=n&&j<=m)
if(a[i]>a[j]){
ans+=n-i+1;
b[k++]=a[j++];
}else
b[k++]=a[i++];
while(i<=n)b[k++]=a[i++];
while(j<=m)b[k++]=a[j++];
for(i=0;i<k;i++)a[left+i]=b[i];//1此处写成 for(i=1;i<k;i++)a[i]=b[i];
}
void merge_sort(int left,int right){
int mid=(left+right)/2;
if(left>=right)return ;
merge_sort(left,mid);
merge_sort(mid+1,right);
memery_sort(left,mid,right);
}
int main(){
int n,i;
scanf("%d",&n);
for(i=0;i<n;i++)
scanf("%d",&a[i]);
merge_sort(0,n-1);
printf("%lld",ans);
return 0;
}
//【例2.5】求逆序对
//采用枚举的方式,猜测能得30分,试试看
//逆序对数量:极限情况 (10^5+0)/2*10^5=5*10^9 cnt采用int要溢出,故cnt采用long long
//提交,测试点1,测试点5-10 运行超时,还算满意2018-3-11 14:45
//高效的逆序对求解方法有两种,归并排序,树状数组,重心先放在归并排序上。
//先举了较为简单的例子1 4 3 2,对归并排序进行了模拟。
//编起代码,模拟还是帮助挺大的
//第一步,编写归并排序代码
//第二步,找逆序对
//样例通过,提交AC 2018-3-11 17:27
#include <stdio.h>
#define LL long long
int a[100100],n,b[100100];
LL cnt=0;
void memory_sort(int left,int mid,int right){//自小到大排序
int i=left,j=mid+1,n=mid,m=right,k=0;//请注意k=0
while(i<=n&&j<=m)
if(a[i]>a[j])b[k++]=a[j++],cnt+=n-i+1;//n-i表示i+1,i+2,...,n-1,n这些数据的个数,n-i+1表示加上i数据
else b[k++]=a[i++];
while(i<=n)b[k++]=a[i++];
while(j<=m)b[k++]=a[j++];
for(i=0;i<k;i++)a[left+i]=b[i];
}
void merge_sort(int left,int right){
int mid=(left+right)/2;
if(left>=right)return;//只剩1个或0个数据,返回
merge_sort(left,mid);//递归
merge_sort(mid+1,right);//递归
memory_sort(left,mid,right);//排序
}
int main(){
int i,j;
scanf("%d",&n);
for(i=1;i<=n;i++)scanf("%d",&a[i]);
merge_sort(1,n);
printf("%lld",cnt);
return 0;
}
//1176 谁考了第k名
#include <stdio.h>
struct node{
int id;
double score;
}stu[110],stu_t;
int main(){
int n,k,i,j;
scanf("%d%d",&n,&k);
for(i=1;i<=n;i++)scanf("%d%lf",&stu[i].id,&stu[i].score);
for(i=1;i<=n;i++)
for(j=i+1;j<=n;j++)
if(stu[i].score<stu[j].score){
stu_t=stu[i];
stu[i]=stu[j];
stu[j]=stu_t;
}
printf("%d %g",stu[k].id,stu[k].score);
return 0;
}
//1177 奇数单增序列
#include <stdio.h>
int a[510];
int main(){
int n,i,j,b,k=0,t;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d",&b);
if(b%2==1)k++,a[k]=b;
}
for(i=1;i<=k;i++)
for(j=i+1;j<=k;j++)
if(a[i]>a[j])t=a[i],a[i]=a[j],a[j]=t;
printf("%d",a[1]);
for(i=2;i<=k;i++)
printf(",%d",a[i]);
return 0;
}
//1178 成绩排序
#include <stdio.h>
#include <string.h>
struct node{
char name[30];
int score;
}stu[30],stu_t;
int main(){
int n,i,j;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%s%d",stu[i].name,&stu[i].score);
for(i=1;i<=n;i++)
for(j=i+1;j<=n;j++)
if(stu[i].score<stu[j].score)stu_t=stu[i],stu[i]=stu[j],stu[j]=stu_t;
else if(stu[i].score==stu[j].score&&strcmp(stu[i].name,stu[j].name)>0)
stu_t=stu[i],stu[i]=stu[j],stu[j]=stu_t;
for(i=1;i<=n;i++)
printf("%s %d\n",stu[i].name,stu[i].score);
return 0;
}
1179 奖学金
3.洛谷 p1093 奖学金
http://blog.csdn.net/mrcrack/article/details/61625530
NOIP 2007 普及组 复赛 scholar 奖学金
1.采用结构体,该题思路就会比较清晰。
2.因总数不超过300,因条件考虑较多,采用冒泡排序处理,代码处理起来比较简单。
3.按照题目要求进行程序书写,即可,注意单独写一个交换函数,可以减少代码量。
附上AC代码,编译环境Dev-C++4.9.9.2
#include <stdio.h>struct node{
int i;//序号
int yu;//语文
int shu;//数学
int ying;//英语
int zong;//总分
}stu[300+10],stu_t;
void swap(int i,int j){
stu_t=stu[i];
stu[i]=stu[j];
stu[j]=stu_t;
}
int main(){
int n;
int i,j,yu,shu,ying;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d%d%d",&yu,&shu,&ying);
stu[i].i=i;
stu[i].yu=yu;
stu[i].shu=shu;
stu[i].ying=ying;
stu[i].zong=yu+shu+ying;
}
for(i=1;i<=n;i++)//自大到小排序
for(j=i+1;j<=n;j++)
if(stu[i].zong<stu[j].zong)
swap(i,j);
else if(stu[i].zong==stu[j].zong)
if(stu[i].yu<stu[j].yu)
swap(i,j);
else if(stu[i].yu==stu[j].yu)
if(stu[i].i>stu[j].i)
swap(i,j);
for(i=1;i<=5;i++)
printf("%d %d\n",stu[i].i,stu[i].zong);
return 0;
}
1180 分数线划定
3.//洛谷 p1068 分数线划定
http://blog.csdn.net/mrcrack/article/details/61625530
NOIP 2009 普及组 复赛 score 分数线划定
1.向下取整,猜测5.5为5.问题读完,发现理解正确。
2.看了数据范围n=9000,n^2=8.1*10^7,用冒泡排序,超时可能性极大,快排出手。
3.采用结构体记录志愿者信息。
4.用快排编写不舒服,马上转向冒泡排序。
5.提交全WA,才发现测试语句未删除,删除后提交,发现错了测试点2,测试点10.
6.找到问题:
if(p[i].s>=p[q-1].s)//此处写成 if(p[i].s>=p[q].s)错了测试点2,测试点10
printf("%d %d\n",p[q-1].s,count);//此处写成 if(p[i].s>=p[q].s)错了测试点2,测试点10
附上AC代码,编译环境Dev-C++4.9.9.2
#include <stdio.h>struct node{
int k;
int s;
}p[10000],mid,t;
int main(){
int n,m,q;
int i,j;
int count=0;
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
scanf("%d%d",&p[i].k,&p[i].s);
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
if(p[i].s<p[j].s){
t=p[i];
p[i]=p[j];
p[j]=t;
}else if(p[i].s==p[j].s){
if(p[i].k>p[j].k){
t=p[i];
p[i]=p[j];
p[j]=t;
}
}
q=m*1.5;
for(i=0;i<n;i++)
if(p[i].s>=p[q-1].s)//此处写成 if(p[i].s>=p[q].s)错了测试点2,测试点10
count++;
else
break;
printf("%d %d\n",p[q-1].s,count);//此处写成 if(p[i].s>=p[q].s)错了测试点2,测试点10
for(i=0;i<count;i++)
printf("%d %d\n",p[i].k,p[i].s);
return 0;
}
//1181 整数奇偶排序
#include <stdio.h>
int a[20],b[20];
int main(){
int i,j,d,m=0,n=0,t;
while(scanf("%d",&d)!=EOF)
if(d%2==1)a[m++]=d;
else b[n++]=d;
for(i=0;i<m;i++)
for(j=i+1;j<m;j++)
if(a[i]<a[j])t=a[i],a[i]=a[j],a[j]=t;
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
if(b[i]>b[j])t=b[i],b[i]=b[j],b[j]=t;
for(i=0;i<m;i++)
printf("%d ",a[i]);
for(i=0;i<n;i++)
printf("%d ",b[i]);
return 0;
}
//1182 合影效果
#include <stdio.h>
#include <string.h>
double a[50],b[50];
char s[20];
int main(){
int n,i,j,p=0,q=0;
double d,t;
scanf("%d",&n);
for(i=0;i<n;i++){
scanf("%s%lf",s,&d);
if(strcmp(s,"male")==0)a[p++]=d;
else b[q++]=d;
}
for(i=0;i<p;i++)
for(j=i+1;j<p;j++)
if(a[i]>a[j])t=a[i],a[i]=a[j],a[j]=t;
for(i=0;i<q;i++)
for(j=i+1;j<q;j++)
if(b[i]<b[j])t=b[i],b[i]=b[j],b[j]=t;
for(i=0;i<p;i++)printf("%.2lf ",a[i]);
for(i=0;i<q;i++)printf("%.2lf ",b[i]);
return 0;
}
//1183 病人排队
//分成老年人,非老年人两组
//该题需要自己标记登记序列
#include <stdio.h>
#include <string.h>
struct node{
char id[20];
int age;
int seq;
}a[110],b[110],t;
int main(){
int n,i,j,d,p=0,q=0,k=0;
char s[20];
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%s%d",s,&d);
k++;
if(d>=60)strcpy(a[p].id,s),a[p].age=d,a[p].seq=k,p++;
else strcpy(b[q].id,s),b[q].age=d,b[q].seq=k,q++;
}
for(i=0;i<p;i++)
for(j=i+1;j<p;j++)
if(a[i].age<a[j].age)t=a[i],a[i]=a[j],a[j]=t;
else if(a[i].age==a[j].age&&a[i].seq>a[j].seq)t=a[i],a[i]=a[j],a[j]=t;
for(i=0;i<q;i++)
for(j=i+1;j<q;j++)
if(b[i].seq>b[j].seq)t=b[i],b[i]=b[j],b[j]=t;
for(i=0;i<p;i++)
printf("%s\n",a[i].id);
for(i=0;i<q;i++)
printf("%s\n",b[i].id);
return 0;
}
1184 明明的随机数
//1184 明明的随机数
//之前代码不好理解,决定重编
//样例通过,提交AC 2018-5-5 15:27
#include <stdio.h>
int a[110],b[110];
int main(){
int n,i,j,t,k;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
for(i=1;i<=n;i++)//自小到大排序
for(j=i+1;j<=n;j++)
if(a[i]>a[j]){
t=a[i];
a[i]=a[j];
a[j]=t;
}
b[1]=a[1],k=1;
for(i=2;i<=n;i++)
if(a[i]!=a[i-1])//去除重复数据
b[++k]=a[i];
printf("%d\n",k);
for(i=1;i<=k;i++)
printf("%d ",b[i]);
return 0;
}
2.洛谷 p1059 明明的随机数
http://blog.csdn.net/mrcrack/article/details/61625530
NOIP 2006 普及组 复赛 random 明明的随机数
1.本题考查排序,因<=100,采用写法比较简单的冒泡排序。
附上AC代码,编译环境Dev-C++4.9.9.2
#include <stdio.h>int main(){
int n;
int i,j,t;
int a[100+10];
int b[100+10];
int count;
scanf("%d",&n);
for(i=0;i<n;i++)
scanf("%d",&a[i]);
for(i=0;i<n;i++)//冒泡排序,自小到大
for(j=i+1;j<n;j++)
if(a[i]>a[j]){
t=a[i];
a[i]=a[j];
a[j]=t;
}
count=1;
b[0]=a[0];
for(i=1;i<n;i++)
if(b[count-1]!=a[i])
b[count++]=a[i];
printf("%d\n",count);
printf("%d",b[0]);
for(i=1;i<count;i++)
printf(" %d",b[i]);
printf("\n");
return 0;
}
//1185 单词排序 2017-10-28 13:19
#include <stdio.h>
#include <string.h>
char a[110][60];
int main(){
int k=0,i,j,p;
char t[60];
while(scanf("%s",a[k])!=EOF)k++;
for(i=0;i<k;i++)
for(j=i+1;j<k;j++)
if(strcmp(a[i],a[j])>0)strcpy(t,a[i]),strcpy(a[i],a[j]),strcpy(a[j],t);
else if(strcmp(a[i],a[j])==0){//单词左移
k--;
for(p=i;p<k;p++)
strcpy(a[p],a[p+1]);
}
for(i=0;i<k;i++)
printf("%s\n",a[i]);
return 0;
}
//1186 出现次数超过一半的数
#include <stdio.h>
#include <string.h>
int a[110];
int main(){
int n,i,b,k,max=-1,tag=0;
memset(a,0,sizeof(a));
scanf("%d",&n);
for(i=0;i<n;i++){
scanf("%d",&b);
a[b+50]++;
}
for(i=0;i<100;i++)
if(a[i]>=(n+1)/2)tag=1,printf("%d ",i-50);
if(tag==0)printf("no");
return 0;
}
//1187 统计字符数
#include <stdio.h>
#include <string.h>
int a[30];
char s[1100];
int main(){
int i,len,max=-1,j;
memset(a,0,sizeof(a));
scanf("%s",s);
len=strlen(s);
for(i=0;i<len;i++)a[s[i]-'a']++;
for(i=0;i<26;i++)
if(a[i]>max)j=i,max=a[i];
printf("%c %d",'a'+j,max);
return 0;
}
该章节总结:除了 1311 【例2.5】求逆序对 其他问题都可以用冒泡排序解决。
是练冒泡排序的好章节。
2017-10-28 22:26 AC该章节内容