這篇主要是記錄一些練習排序 的題目

Insertion Sort

c010. 10107 - What is the Median?

這題的話,因為每塞入一個數就要求中位數,也就是每塞入一個數就要重新排序並算出中位數。這時候insertion sort就會是最好的方法,因為如果用quick sort、merge sort這些公認比較快的方法,每次整理的時間複雜度為O(nlogn),而insertion sort只需要O(n),相比之下快了很多,而且也比較容易實作。

AC (13ms, 108KB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>

int main(){
int n,cnt=0,num[10000]={0},i;
while(scanf("%d",&n) != EOF){
for(i=cnt-1;num[i]>n;i--){
num[i+1] = num[i];
}
num[i+1] = n;
cnt++;
if(cnt%2){
printf("%d\n",num[cnt/2]);
}
else{
printf("%d\n",(num[cnt/2-1]+num[cnt/2])/2);
}
}
return 0;
}

Counting Sort

a941. 10041 - Vito’s large family

哪個點能滿足從他的家到所有的親戚的家的距離的和為最小 ?位於中位數的點就是這個萬中選一的角色。所以我們只要能找到中位數,然後累加每個點到中位數的距離 ,就能解決這題。

解題想法:
1.先看r是奇還是偶,奇的話只會有一個中位數,偶的話會有兩個。
2.若為奇,先找到所有門牌號碼的中位數,然後累加每個點與它的距離。
3.累加的策略:

跑一個0~30000的迴圈,每次直接相加所有i號到中位數門牌的距離。
距離 += | (中位數 - i) * (門牌為i的房子數量) |
因為(中位數-i)可能為負,所以用絕對值。

4.若為偶,就先用第2步、第3步的方法算出較小中位數的距離,並判斷較大中位數是否和較小中位數相等。

這邊的較大較小中位數指的是順位,假設有8個從小到大排好的門牌號碼,那第4個就是較小中位數,第5個就是較大中位數。

5.如果較大較小相等,直接輸出第4步算出來的結果。
6.如果較大較小不相同,則用第2步、第3步的方法算出較大中位數的距離,再來比較兩個中位數算出來的距離,並輸出距離較小的那個。

AC (0.4s, 212KB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include<stdio.h>
#include<stdlib.h>

int main(){
int t,r,i,x;
scanf("%d",&t);
while(t--){
int s[30001]={0},cnt=0,loc,lod;
scanf("%d",&r);
for(i=0;i<r;i++){
scanf("%d",&x);
s[x]++;
}

long long d=0;
//r為奇數
if(r%2){
//先找到新家的門牌號碼
for(i=0;i<30001;i++){
cnt += s[i];
if(cnt >= r/2+1){
loc = i;
break;
}
}

//再算出距離
for(i=0;i<30001;i++){
if(s[i]){
d += abs((loc-i)*s[i]);
}
}

printf("%lld %d\n",d,loc);
}

//r為偶數
else{
long long dd=0;
//左中位數
//先找到新家的門牌號碼
for(i=0;i<30001;i++){
cnt += s[i];
if(cnt >= r/2){
loc = i;
break;
}
}
//再算出距離
for(i=0;i<30001;i++){
if(s[i]){
d += abs((loc-i)*s[i]);
}
}

//右中位數
for(i=0;i<30001;i++){
cnt += s[i];
if(cnt >= r/2+1){
lod = i;
break;
}
}
//如果右中位數和左中位數不同,則也以右中位數為基準算出距離,再比較大小。
if(loc != lod){
for(i=0;i<30001;i++){
if(s[i]){
dd += abs((lod-i)*s[i]);
}
}

if(d <= dd){printf("%lld %d\n",d,loc);}
else{printf("%lld %d\n",dd,lod);}
}
//相同就直接輸出
else{
printf("%lld %d\n",d,loc);
}
}
}
return 0;
}

e507. 10252 - Common Permutation

本來想說用LCS做,但我突然想到我只要統計這兩個字串的每個字母出現多少次,然後再照順序輸出就好,這樣的做法和counting sort很像,所以把這題歸在這邊。

AC (3ms, 72KB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<stdio.h>
#include<string.h>
#define min(a,b) a<b?a:b

int main(){
char a[1001],b[1001];
while(scanf("%s%s",a,b) != EOF){
int acnt[26]={0}, bcnt[26]={0},i,j;
int al=strlen(a),bl=strlen(b),t;
for(i=0;i<al;i++){
acnt[a[i]-97]++;
}
for(i=0;i<bl;i++){
bcnt[b[i]-97]++;
}
for(i=0;i<26;i++){
t = min(acnt[i], bcnt[i]);
for(j=0 ; j<t ; j++){
printf("%c",i+97);
}
}
printf("\n");
}
return 0;
}

Others

d750. 11321 - Sort! Sort!! and Sort!!!

這題很明顯的就是告訴我們一堆排序的規則,排序完就印出來,然後要注意輸入的ab可能為負,如果用C、C++,取得的餘數也會因此為負,所以我會先變成絕對值,這樣比較好處理。

AC (28ms, 356KB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<bits/stdc++.h>
using namespace std;

int num[10005],n,m;

bool cmp(int a, int b){
//ab可能為負,所以先變成絕對值
int am = abs(a%2),bm = abs(b%2);

//每個數字除以M的餘數由小到大排
if(a%m != b%m){return a%m < b%m;}

//排序中比較的兩數為一奇一偶
if(am != bm){return a%2;}

//兩奇數除以M餘數大小相等,則原本數值較大的奇數排在前面。
if(am && bm){return a > b;}

//兩偶數除以M餘數大小相等,則較小的偶數排在前面。
return a < b;
}

int main(){
int i;
while(cin>>n>>m && n+m){
for(i=0;i<n;i++){cin>>num[i];}
sort(num, num+n, cmp);
cout<<n<<" "<<m<<"\n";
for(i=0;i<n;i++){
cout<<num[i]<<"\n";
}
}
cout<<"0 0\n";
return 0;
}

a539. 10327 - Flip Sort

在討論區裡,可以看到有些人是先計次數,再排序,而我的作法是只算前面有幾個比自己大,然後把所有的結果相加印出,省下了排序的步驟。以下將證明我的想法可行:

我們令一陣列裡有{3,4,1,5,2}這五個數,且用sum表示交換次數。
圖一:討論區普遍的做法

圖二:我的做法

AC (3ms, 88KB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>

int main(){
int n,i,j,num[1001];
while(scanf("%d",&n)!=EOF){
int chg=0;
for(i=0;i<n;i++){scanf("%d",&num[i]);}

//check
for(i=1;i<n;i++){
for(j=i-1;j>=0;j--){
//前面的數比自己大
if(num[j] > num[i]){chg++;}
}
}

//output
printf("Minimum exchange operations : %d\n",chg);
}
return 0;
}

c012. 10062 - Tell me the frequencies!

程式碼有點複雜,我真的想不到怎麼精簡…

AC (4ms, 348KB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<bits/stdc++.h>
#define speedup ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
using namespace std;

struct sChar{
int ascii,cnt;
};

bool cmp(sChar a, sChar b){
if(a.cnt == b.cnt){
return a.ascii > b.ascii; //2個以上的字元有相同的次數,則ASCII值較大的先輸出。
}
return a.cnt < b.cnt; //根據出現的次數由小到大輸出
}

int main(){
speedup;
string s;
int first=0;
while(getline(cin, s)){
//如果這筆測資已不是第一筆,就輸出一行空白行。
if(first){cout<<"\n";}

int asc[129]={0},i,j,sl=s.length();
//統計每個字元的個數
for(i=0;i<sl;i++){
asc[s[i]]++;
}

//用chr儲存所有個數不為0的字元
int idx=0;
sChar chr[129];
for(i=0;i<129;i++){
if(asc[i]){
chr[idx].ascii = i;
chr[idx].cnt = asc[i];
idx++;
}
}

//按照題目所求排序chr
sort(chr,chr+idx,cmp);
for(i=0;i<idx;i++){
cout<<chr[i].ascii<<" "<<chr[i].cnt<<"\n";
}

if(!first){first = 1;}
}
return 0;
}

封面圖源:Pixiv