今天上午模擬賽的時候,(十分錯誤地)判斷有一道題可以用LCA混點分(然而還不如直接爆搜得分高),在敲那個LCA的程式碼時突然想起來我好像還沒有寫過LCA,想了想,是該給我的LCA寫點東西了呢。
但是!不出意外的,出了億點點意外,就是我在敲板子題的時候發現經過一年的荒廢,我已經完全不會鏈式前向星。好不容易搗鼓了半天,終於還是沒調出來,於是我只能十分痛心疾首地去好好學習了一下。
所以!本來今天要說說LCA的,但是最終我決定還是先講講圖論最最基礎,也是最為致命(啊,就是說我因為不會鏈式前向星導致我沒把LCA整出來)的東西之一,存圖!
這裡只介紹兩種常用的方法,如有需要的的同學可以看看別人的拓展。
--------------------------------------分割線---------------------------------------------
一,鄰接矩陣
鄰接矩陣顧名思義,是一個矩陣(廢話),在老一點的演演算法書裡面常常能見到用這種方法存圖的程式碼。優點是特別簡單好理解,用起來也是非常的方便;缺點在於空間複雜度是n^2級的。
舉個例子吧,有如下無向連通圖(每邊邊長為1)
首先我們要開一個陣列f[mm][mm],其中f[i][j]表示點i到點j之間連邊的長度,如下圖
另外,很顯然一個點到它自己的距離是零
因為這是一個無向圖,所以存圖的時候要注意f[i][j]=f[j][i]這一點,即,同一邊要存兩次;
如果說點i和點j之間沒有直接的邊那應該如何表示呢?很簡單,初始化為inf,兩個點之間距離無限遠不就是說兩點之間不能互相直接到達嗎,所以見如下程式碼
1 #include<bits/stdc++.h>
2 using namespace std;
3 const int mm=205;//並沒有什麼特殊意義
4 const int inf=1e9+7;
5 int f[mm][mm];
6 int n,m;//有n個點,m條邊
7 int main()
8 {
9 cin>>n>>m;
10 for(int i=1;i<=n;i++)
11 for(int j=1;j<=n;j++)
12 {
13 f[i][j]=inf;//初始化為任意兩點之間距離為inf
14 }
15 for(int i=1;i<=m;i++)
16 {
17 int x,y;
18 cin>>x>>y;//將兩個點相連
19 f[x][y]=1;
20 f[y][x]=1;
21 }
22 return 0;
23 }
二、鏈式前向星
鏈式前向星近些年非常常見,它的誕生讓圖論題的資料範圍大幅提高(真是太糟糕了),相比於鄰接矩陣,鏈式前向星確實會更復雜、更難自己想(指的是我自己調半天都不知道問題在哪),但一個標準的鏈式前向星的程式碼量其實很少,佔用的空間也會少得多
鄰接矩陣實際上是基於點的存圖,它儲存的是點與點之間的連邊,而鏈式前向星是基於邊的存圖,它儲存的是一條條的邊,每條邊的兩個端點分起點終點。
我們選擇使用結構體(結構體介紹:https://www.cnblogs.com/qj-Network-Box/p/13138534.html)來實現存邊的操作,一條邊的要素,無非是起點終點和長度,由於上圖每條邊的長度一樣,暫時先不考慮長度的問題
1 const int mm=205;//沒有什麼特別的意義
2 struct node{
3 int s,v;//從點s到點v
4 }a[mm];
很顯然,鏈式前向星,身為一個用鏈開頭的詞,和連結串列多多少少有點聯絡
而這個聯絡就在於:同一起點的邊被排成一條鏈。原因很顯然,方便查詢
我們回到這幅圖
按照起點分類,可以變成以下幾串
比如,要實現查詢到點1到點2的路徑後能找到點1到點3的路徑,就可以在存點1到點2路徑的結構體裡面新增一個nex元素,nex指向下一個以1為起點的元素
那麼問題來了,我們一開始怎麼找到點1到點2的那條邊呢(身為該起點被存入的第一條邊,沒有nex指向它),我們需要一個head[x]陣列來儲存起點為x的第一條邊的序號
顯然既然是同一起點的,在存邊的時候就不需要把起點算進去了
const int mm=205;//沒有什麼特別的意義
struct node{
int v,nex;
}a[mm*2];
int head[mm];//用於儲存最頭部邊的序號
考慮遍歷,以x為起點的第一條邊編號為head[x],下一條就應該是a[head[x]].nex,再下一條就是a[a[head[x]].nex].nex,直到某個節點的nex指向0
程式碼實現如下
#include<bits/stdc++.h>
using namespace std;
int tot;
int n;
const int mm=205;//沒有什麼特別的意義
struct node{
int v,nex;//從點x到點v
}a[mm*2];
int head[mm];//用於儲存第一條邊的序號
void add(int x,int y) //一條x到y的邊
{
a[++tot].v=y;//目標是y
a[tot].nex=head[x];//將上一條邊的nex指向這一條邊
head[x]=tot;//更新
}
void pp(int x)
{
for(int i=head[x];i!=0;i=a[i].nex)//以head[x]為第一邊,每次跳轉到nex所指的下一條邊
{
cout<<x<<" "<<a[i].v<<endl;//輸出起點和終點
}
return ;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
int x,y;
cin>>x>>y;
add(x,y),add(y,x);//無向圖要存兩邊
}
for(int i=1;i<=n;i++)//輸出所有的邊
{
pp(i);
}
return 0;
}