最小生成樹,Kruskal算法。
算法很簡單,先把邊排序,依次找鏈接不同集合的最小邊,合并集合,當只有一個集合的時候結(jié)束。問題在于如何實現(xiàn)集合合并,學長們說合并時用并查集效率較高。我這里用不同的數(shù)字代表不同的集合,每次合并都要遍歷所有集合,改變集合數(shù)字,時間復雜度O(n)。
Ege結(jié)構體中剛開始把b、d兩個變量定義成了char,數(shù)據(jù)小的時候沒問題,當數(shù)據(jù)大于127時就會爆掉,糾結(jié)了很久。
qsort()函數(shù)用法:void qsort(void *base, int nelem, int width, int (*fcmp)(const void *,const void *));
base是數(shù)組起始下標;
nelem是元素個數(shù);
width是單個元素的大小;
fcmp是比較函數(shù)。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char mp[6][6];//map
int len;//map length
int mb;//bigesst
int mbt;//now road length
int CP(int x, int y)//canput
{
int i;
i = y - 1;
while(i >= 0 && mp[x][i] != 'X')
{
if(mp[x][i] == 'O')
return 0;
i--;
}
i = y + 1;
while(i < len && mp[x][i] != 'X')
{
if(mp[x][i] == 'O')
return 0;
i++;
}
i = x - 1;
while(i >= 0 && mp[i][y] != 'X')
{
if(mp[i][y] == 'O')
return 0;
i--;
}
i = x + 1;
while(i < len && mp[i][y] != 'X')
{
if(mp[i][y] == 'O')
return 0;
i++;
}
return 1;
}
void DFS(int n)
{
int i, j;
int x, y;
if(n == len * len)
{
if(mb < mbt)
mb = mbt;
return ;
}
x = n / len;
y = n % len;
if(mp[x][y] == '.' && CP(x, y))
{
mp[x][y] = 'O';
mbt++;
DFS(n + 1);
mbt--;
mp[x][y] = '.';
DFS(n + 1);
}
else
DFS(n + 1);
}
int main()
{
int i, j;
scanf("%d", &len);
getchar();
while(len != 0)
{
for(i = 0; i < len; i++)//read map
gets(mp[i]);
mbt = mb = 0;
DFS(0);

printf("%d\n", mb);
scanf("%d", &len);
getchar();
}
}
這道題跟之前走迷宮的題略有不同,走迷宮時起始點確定,當前點可走的方向確定。而這道題結(jié)束條件是判斷過的格數(shù)超過總格數(shù)。
即使是合法的點也可以選擇不放炮臺。
這是《算法設計與分析》教材上的一道題,我們老師布置的第一道題。說的是統(tǒng)計出一本給定頁碼書中從0~9各個數(shù)字出現(xiàn)的次數(shù),頁碼最高不差過10e9。
窮舉法是很容易想到的,不過當輸入過大時很耗時間。因此應該總結(jié)規(guī)律。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#define LEN 20
int bs[] = {0, 1, 20, 300, 4000, 50000, 600000, 7000000, 80000000, 900000000};
int BS = 1111111111;
void StrtoNum(char *str, int *num)
{
int i, len;
len = strlen(str);
*num = 0;
for(i = 0; i < len; i++)
*num = *num * 10 + str[i] - '0';
}
int Pow(int a, int b)
{
int i, t;
t = a;
for(i = 0; i < b - 1; i++)
t *=a;
return t;
}
int GetMod(int a)
{
return BS % Pow(10, a);
}
int main()
{
int i, j;
int nb;// now bit
char nums[LEN];
int num;
int rs[10];// result
int len;
int mh;//most high
int nt;
while(gets(nums) != NULL)
{
memset(rs, 0, sizeof(rs));
len = strlen(nums);
StrtoNum(nums, &num);
//
//printf("num = %d\n", num);
//
mh = nums[len - 1] - '0';
for(i = 0; i <= mh; i++)//init the lowest bit
rs[i] = 1;
for( i = 1; i < len; i++)
{
nb = len - i -1;
mh = nums[nb] - '0';
StrtoNum(&nums[nb + 1], &nt);
//
//printf("mh = %d nt = %d\n", mh, nt);
//
rs[mh] += nt + 1;//@2, mh mh
for(j = 0; j < mh; j++)//@2 others
{
rs[j] += Pow(10, i);
}
for(j = 0; j < 10; j++)//@1
{
rs[j] += mh * bs[i];
}
}
rs[0] -= GetMod(len);
for(i = 0; i < 10; i++)
printf("%d %d\n", i, rs[i]);
}
//getchar();
}
統(tǒng)計出只有一位數(shù)的情況是很簡單的,我們來當在統(tǒng)計好的一個數(shù)字前面再加上位數(shù)時統(tǒng)計結(jié)果會怎么增加。我們可以把這個增加的值看做兩部分,一部分是因高位增加導致地位數(shù)的取值范圍增大而導出的,另一部分是高位本身產(chǎn)生的。兩方面的計算都有規(guī)律可循。要特別注意0的計算。
請注意庫函數(shù)pow()的返回值為double,轉(zhuǎn)換為int時會有精度丟失(調(diào)試中的表現(xiàn)為無論數(shù)據(jù)多大,結(jié)果總跟標準答案相差1),因此這里特地寫了一個Pow()函數(shù)做返回值為int的乘方計算。
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3171
據(jù)說這道題用的是動態(tài)規(guī)劃,首先把"s"看成要找的串,其次"se",其次'sev',直到"seven",只需將元串掃描5遍即可得到結(jié)果。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define LEN 10005
int main()
{
char s1[LEN];
long long rs[6][LEN];
char s0[7] = "@seven";
char s2[7] = "@SEVEN";
int i, j;
int len;
//printf("%d\n", sizeof(long long));
while(gets(s1) != NULL)
{
memset(rs, 0, sizeof(rs));
for(i = 0; i < LEN; i++)
rs[0][i] = 1;
len = strlen(s1);
for(i = 1; i < 6; i++)
for(j = 0; j < len; j++)
{
if(s1[j] == s0[i] || s1[j] == s2[i])
rs[i][j + 1] = rs[i][j] + rs[i - 1][j];
else
rs[i][j + 1] = rs[i][j];
}
printf("%lld\n", rs[5][len]);
/*for(i = 0; i < 6; i++)
{
for(j = 0; j < len + 2; j++)
printf("%ld ", rs[i][j]);
putchar(10);
}*/
}
}

再次印證了我的菜鳥身份,long long 的輸出格式為"%lld",為此WA了三次。
另外關于遞歸要說一點,當遞歸中涉及到對全局變量(比如一個全局的數(shù)組)的修改時,之前我一直存在的疑問是:如果遞歸式樹狀調(diào)用結(jié)構,那么每一時刻這個全局的數(shù)組在被誰使用呢?如果每層遞歸都能同時使用該數(shù)組,那數(shù)據(jù)不就亂了嗎?原來雖然遞歸的調(diào)用可以是樹狀的,但是每一個時刻遞歸都是沿著遞歸樹中的一條路在走,一條路走不通了才會退一步,換個子樹接著走。這些都是在了解回溯之后才想通的。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct
{
char x;
char y;
}Node;

int fd;//have find given length
int T;
int len;
char mp[8][8];//map
void f(int x, int y)
{
if(!fd)
{
if(mp[x - 1][y] == 'D' && len + 1 == T)fd = 1;
else if(mp[x - 1][y] == '.')//go up
{
len ++;
mp[x - 1][y] = 'X';
f(x - 1, y);
len --;
mp[x - 1][y] = '.';
}
if(mp[x][y + 1] == 'D' && len + 1 == T)fd = 1;
else if(mp[x][y + 1] == '.')//go right
{
len ++;
mp[x][y + 1] = 'X';
f(x, y + 1);
len --;
mp[x][y + 1] = '.';
}
if(mp[x + 1][y] == 'D' && len + 1 == T)fd = 1;
else if(mp[x + 1][y] == '.')//go down
{
len ++;
mp[x + 1][y] = 'X';
f(x + 1, y);
len --;
mp[x + 1][y] = '.';
}
if(mp[x][y - 1] == 'D' && len + 1 == T)fd = 1;
else if(mp[x][y - 1] == '.')//go left
{
len ++;
mp[x][y - 1] = 'X';
f(x, y - 1);
len --;
mp[x][y - 1] = '.';
}
}
}
int main()
{
int N, M;
int i, j;
int find;
Node s;
scanf("%d%d%d", & N, & M, & T);
while(N + M + T != 0)
{
for(i = 1; i <= N; i++)//read map
scanf("%s",&mp[i][1]);
for(i = 1; i <= N; i++)
for(j = 1; j <= M; j++)
if(mp[i][j] == 'X')
for(i = 0; i <= N + 1; i++)//init map
mp[i][0] = mp[i][M + 1] = 'X';
for(i = 1; i <= M; i++)
mp[0][i] = mp[N + 1][i] = 'X';
find = 0;
for(i = 1; i <= N && find == 0; i++)//search start point
for(j = 1; j<= M && find == 0; j++)
if(mp[i][j] == 'S')
{
s.x = i;
s.y = j;
find = 1;
}
fd = 0;
len = 0;
f(s.x, s.y);
if(fd == 1)
puts("YES");
else
puts("NO");
scanf("%d%d%d", & N, & M, & T);
}
}

問題的關鍵是題目要求沒有給清楚,沒有給出輸入數(shù)據(jù)的范圍。應該先將數(shù)字當做字符串處理。
#include<stdio.h>
#include<string.h>
int root2(char *s)
{
int sum2 = 0;
int i, len = strlen(s);
for(i = 0; i < len; i++)
sum2 += s[i] - '0';
return sum2;
}
long root(long n)
{
long sum = 0;
while(n)
{
sum += n % 10;
n /= 10;
}
return sum;
}
int main()
{
long sum;
char s[1000];
gets(s);
while(strcmp(s, "0"))
{
sum = root2(s);
while(sum / 10)
{
sum = root(sum);
}
printf("%d\n", sum);
gets(s);
}
}

算法很簡單,先把邊排序,依次找鏈接不同集合的最小邊,合并集合,當只有一個集合的時候結(jié)束。問題在于如何實現(xiàn)集合合并,學長們說合并時用并查集效率較高。我這里用不同的數(shù)字代表不同的集合,每次合并都要遍歷所有集合,改變集合數(shù)字,時間復雜度O(n)。
Ege結(jié)構體中剛開始把b、d兩個變量定義成了char,數(shù)據(jù)小的時候沒問題,當數(shù)據(jù)大于127時就會爆掉,糾結(jié)了很久。
qsort()函數(shù)用法:void qsort(void *base, int nelem, int width, int (*fcmp)(const void *,const void *));
base是數(shù)組起始下標;
nelem是元素個數(shù);
width是單個元素的大小;
fcmp是比較函數(shù)。

C語言中,文件讀寫相關的函數(shù)有很多個,但是從讀寫的數(shù)據(jù)形式來說可以分為兩類:二進制和文本。關于文本讀寫函數(shù)不多說了,只要會使用格式化的輸入輸出fscanf()、fprintf()就基本可以解決問題。這里主要說一下二進制的文件讀寫函數(shù)fread()和fwrite()。
函數(shù)原型分別為:
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
size_t fread(void* buffer, size_t size, size_t count, FILE* stream);
其中
buffer是存儲數(shù)據(jù)的指針
size是單個元素的大小(單位是字節(jié))
count是元素的個數(shù)
stream是文件指針
函數(shù)的返回值是實際讀取或?qū)懭朐氐膫€數(shù)。
需要注意的是打開供二進制讀寫的文件時讀寫方式后面要多加一個"b",表示二進制讀寫。例如打開供二進制寫入的文件可以為fp = fopen("out.txt", "wb");
用二進制存儲文件可以在一定程度上起到文件的保密作用。如果別人用文本編輯器打開我們存儲的二進制代碼,ta看到的很可能都是些亂碼。這里之所以所很可能是應為如果我們存入的本來就是文本(char類型)的話,別人還是能夠看到里面的內(nèi)容的。這是因為char的存入是以ASCII的形式存的,這些編碼能夠被文本編輯器識別。但其他的類型就不行了。
我們來舉一個例子:
比如int a = 64(假設int占兩個字節(jié)),64的二進制為00000000 01000000,若用文本打開,編輯器會試將a顯示為兩個字符,一個ASCII為0的字符,和一個ASCII為64的字符。0對應的ASCII為null,沒有顯示;64對應的ASCII為字符@, 這是我們能看到的。
如果我們選擇用文本存儲a,系統(tǒng)不會把a看成數(shù)字,而會看成由兩個字符組成的序列:'6'和'4'。'6'的ASCII為54,二進制就是00110110,'4'的ASCII為52,二進制為00110100。因此a的文本存儲形式對應的二進制就是00110110 00110100(要明白,所有數(shù)據(jù)在計算機里其實都是以二進制存儲的)。
當然,二進制存儲文件的根本目的是為了更快速的讀寫數(shù)據(jù),因為計算機“喜歡”二進制。要想給數(shù)據(jù)加密還必須有加密算法才行。
摘要: 題意:求出將上面的數(shù)字變成下面的數(shù)字所需的最少路數(shù)。變換方式只有“加”,“減”,“交換”三種。一道很普通的廣搜題。用used[]記錄各節(jié)點的層數(shù),以及判斷該結(jié)點是否被訪問過(used[i] == 0 表示沒有訪問過。特別注意初始節(jié)點的層數(shù)為0,但它被訪問過,因此要特殊處理一下。)
Code highlighting prod... 閱讀全文
深度加回溯,類似于八皇后問題。
函數(shù)原型分別為:
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
size_t fread(void* buffer, size_t size, size_t count, FILE* stream);
其中
buffer是存儲數(shù)據(jù)的指針
size是單個元素的大小(單位是字節(jié))
count是元素的個數(shù)
stream是文件指針
函數(shù)的返回值是實際讀取或?qū)懭朐氐膫€數(shù)。
需要注意的是打開供二進制讀寫的文件時讀寫方式后面要多加一個"b",表示二進制讀寫。例如打開供二進制寫入的文件可以為fp = fopen("out.txt", "wb");
用二進制存儲文件可以在一定程度上起到文件的保密作用。如果別人用文本編輯器打開我們存儲的二進制代碼,ta看到的很可能都是些亂碼。這里之所以所很可能是應為如果我們存入的本來就是文本(char類型)的話,別人還是能夠看到里面的內(nèi)容的。這是因為char的存入是以ASCII的形式存的,這些編碼能夠被文本編輯器識別。但其他的類型就不行了。
我們來舉一個例子:
比如int a = 64(假設int占兩個字節(jié)),64的二進制為00000000 01000000,若用文本打開,編輯器會試將a顯示為兩個字符,一個ASCII為0的字符,和一個ASCII為64的字符。0對應的ASCII為null,沒有顯示;64對應的ASCII為字符@, 這是我們能看到的。
如果我們選擇用文本存儲a,系統(tǒng)不會把a看成數(shù)字,而會看成由兩個字符組成的序列:'6'和'4'。'6'的ASCII為54,二進制就是00110110,'4'的ASCII為52,二進制為00110100。因此a的文本存儲形式對應的二進制就是00110110 00110100(要明白,所有數(shù)據(jù)在計算機里其實都是以二進制存儲的)。
當然,二進制存儲文件的根本目的是為了更快速的讀寫數(shù)據(jù),因為計算機“喜歡”二進制。要想給數(shù)據(jù)加密還必須有加密算法才行。



















































































即使是合法的點也可以選擇不放炮臺。
這是《算法設計與分析》教材上的一道題,我們老師布置的第一道題。說的是統(tǒng)計出一本給定頁碼書中從0~9各個數(shù)字出現(xiàn)的次數(shù),頁碼最高不差過10e9。
窮舉法是很容易想到的,不過當輸入過大時很耗時間。因此應該總結(jié)規(guī)律。









































































請注意庫函數(shù)pow()的返回值為double,轉(zhuǎn)換為int時會有精度丟失(調(diào)試中的表現(xiàn)為無論數(shù)據(jù)多大,結(jié)果總跟標準答案相差1),因此這里特地寫了一個Pow()函數(shù)做返回值為int的乘方計算。
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3171
據(jù)說這道題用的是動態(tài)規(guī)劃,首先把"s"看成要找的串,其次"se",其次'sev',直到"seven",只需將元串掃描5遍即可得到結(jié)果。





































再次印證了我的菜鳥身份,long long 的輸出格式為"%lld",為此WA了三次。
簡單的走迷宮,廣搜求最短路徑,要把坐標搞清楚。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define LEN 14
#define QLEN 100000
typedef struct
{
int x;
int y;
int z;
}Point;
typedef struct
{
int f;
int r;
Point *p;
}Queue;
int d[6][3] =
{
0, 0, 1,
0, 0, -1,
1, 0, 0,
-1, 0, 0,
0, 1, 0,
0, -1, 0
};
char sp[LEN][LEN][LEN];//space map
int rl[LEN][LEN][LEN];//road length
Point bg, ed;
int N;
void BFS()
{
int i, j;
Point t;
int x, y, z;
int find = 0;
Queue q;
q.f = q.r = 0;
q.p = (Point*)malloc(sizeof(Point) * QLEN);
q.p[q.f] = bg;
q.r++;
while(q.f != q.r && !find)
{
t = q.p[q.f];
q.f = (q.f + 1) % QLEN;//DeQueue
for(i = 0; i < 6; i++)
{
x = t.x + d[i][0];
y = t.y + d[i][1];
z = t.z + d[i][2];
if(sp[z][y][x] == 'O')//can walk
{
sp[z][y][x] = 'X';//change mp
rl[z][y][x] = rl[t.z][t.y][t.x] + 1;//change rl
q.p[q.r].x = x;//EnQueue
q.p[q.r].y = y;
q.p[q.r].z = z;
q.r = (q.r + 1) % QLEN;
}
else if(sp[z][y][x] == 'E')
{
rl[z][y][x] = rl[t.z][t.y][t.x] + 1;//change rl
find = 1;
}
}
}
free(q.p);
}
int main()
{
int i, j, k, m;
char s1[LEN];
int gard = 100;
while(scanf("%s%d", s1, &N) == 2 && gard--)
{
getchar();
for(i = 1; i <= N; i++)//read space map
for(j = 1; j <= N; j++)
{
for(k = 1; k <= N; k++)
sp[i][j][k] = getchar();
getchar();
}

scanf("%d%d%d", &bg.x, &bg.y, &bg.z);//read point
scanf("%d%d%d", &ed.x, &ed.y, &ed.z);
getchar();
gets(s1);//read END
//getchar();
bg.x += 1;
bg.y += 1;
bg.z += 1;
ed.x += 1;
ed.y += 1;
ed.z += 1;
sp[bg.z][bg.y][bg.x] = 'B';
sp[ed.z][ed.y][ed.x] = 'E';

for(i = 0; i <= N + 1; i++)//init map
for(j = 0; j <= N + 1; j++)
{
sp[i][j][0] = sp[i][j][N + 1] = sp[N + 1][i][j] = '#';
sp[0][i][j] = sp[i][0][j] = sp[i][N +1][j] = '#';
}

for(i = 0; i < LEN; i++)//init road length
for(j = 0; j < LEN; j++)
for(k = 0; k < LEN; k++)
rl[i][j][k] = 0;
BFS();
if(rl[ed.z][ed.y][ed.x] != 0)
printf("%d %d\n", N, rl[ed.z][ed.y][ed.x]);
else if(bg.x == ed.x && bg.y == ed.y && bg.z == ed.z)
printf("%d 0\n", N);
else
printf("NO ROUTE\n");
}
}
這道題交了很多遍一直WA,很是郁悶。剛開始以為自己的隊列沒有管理好,換成STL隊列問題依舊,又懷疑輸出格式的問題,修改后問題依舊。最后終于看到BFS()中有一個break(寫在了for循環(huán)里面),這樣是跳不出while的,用標志find代替break后果然AC!
想和寫之間的確有很大的差距,多些代碼才是硬道理。
摘要: 求最短路徑,最直接的廣度優(yōu)先搜索。
Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->#include<stdio.h>#include<string.h>#include<stdlib.h>#define ... 閱讀全文
深度優(yōu)先搜索加回溯。之前用四叉樹的記錄遍歷迷宮的所有路徑,結(jié)果內(nèi)存超限制了,請教學長之后才知道有種算法設計方法叫“回溯”,即在沿一條路深度遍歷后再把走過的路標記回來,這樣就能避免影響其它路徑的遍歷。























































































































想和寫之間的確有很大的差距,多些代碼才是硬道理。
scanf("%s",s1)讀取字符串時對前面的空白符有過濾作用,并且字符串中間的空白符將被認為字符串的結(jié)束標志,空白符不會被讀入
gets(s1)讀取字符串時對前面和中間的空白符都沒有過濾,只有換行符才會被認為是字符串的結(jié)束標志,該換行符不被認為是字符串的一部分
另外關于遞歸要說一點,當遞歸中涉及到對全局變量(比如一個全局的數(shù)組)的修改時,之前我一直存在的疑問是:如果遞歸式樹狀調(diào)用結(jié)構,那么每一時刻這個全局的數(shù)組在被誰使用呢?如果每層遞歸都能同時使用該數(shù)組,那數(shù)據(jù)不就亂了嗎?原來雖然遞歸的調(diào)用可以是樹狀的,但是每一個時刻遞歸都是沿著遞歸樹中的一條路在走,一條路走不通了才會退一步,換個子樹接著走。這些都是在了解回溯之后才想通的。

































































































問題的關鍵是題目要求沒有給清楚,沒有給出輸入數(shù)據(jù)的范圍。應該先將數(shù)字當做字符串處理。






































| |||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
---|---|---|---|---|---|---|---|---|---|
29 | 30 | 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 | 1 | 2 | |||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
常用鏈接
隨筆分類(111)
- C語言(3)
- DP(9)
- Java筆記(1)
- Java基礎練習(25)
- 安卓(1)
- 本科畢設(1)
- 博弈(1)
- 大數(shù)(7)
- 回溯(2)
- 排序(10)
- 暑期培訓周賽(3)
- 數(shù)據(jù)結(jié)構(7)
- 數(shù)論(1)
- 水題(8)
- 圖論(24)
- 網(wǎng)選訓練(8)
隨筆檔案(127)
- 2014年3月 (1)
- 2013年7月 (10)
- 2013年5月 (1)
- 2013年4月 (11)
- 2013年3月 (8)
- 2012年10月 (1)
- 2012年9月 (12)
- 2012年8月 (38)
- 2012年7月 (14)
- 2012年6月 (2)
- 2012年5月 (8)
- 2012年4月 (6)
- 2012年3月 (6)
- 2012年2月 (4)
- 2011年8月 (5)
friends
最新評論

- 1.?re: 線段樹
-
是這個樣子的,所以在OJ有時候“卡住”了也不要太灰心,沒準真的不是自己的原因呢。
加油,祝你好運啦! - --小鼠標
- 2.?re: 線段樹
- 對于編程競賽來說,Java所需時間一般為C/C++的兩倍。合理的競賽給Java的時間限制是給C/C++的兩倍。
- --傷心的筆
- 3.?re: poj1273--網(wǎng)絡流
- 過來看看你。
- --achiberx
- 4.?re: (轉(zhuǎn))ubuntu11.10無法啟動無線網(wǎng)絡的解決方法
- 膜拜大神。。查了一個下午資料終于在這里解決了問題。。神牛說的區(qū)域賽難道是ACM區(qū)域賽。。?
- --Hang
- 5.?re: 快速排序、線性時間選擇
- 博主,謝謝你的文章。你的方法可以很好的處理分區(qū)基準在數(shù)組中重復的情況,書上的方法遇到這種輸入會堆棧溢出。書上給出了解釋但給的方法貌似不簡潔。
- --lsxqw2004