日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

清北NOIP训练营集训笔记——图论(提高组精英班)

發布時間:2024/4/14 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 清北NOIP训练营集训笔记——图论(提高组精英班) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

清北NOIP訓練營集訓筆記——圖論(提高組精英班)

本文摘自清北學堂內部圖論筆記,作者為潘愷璠,來自柳鐵一中曾參加過清北訓練營提高組精英班,筆記非常詳細,特分享給大家!更多信息學資源關注微信訂閱號noipnoi。

最短路徑:

1.Floyd算法(插點法):

通過一個圖的權值矩陣求出它的每兩點間的最短路徑(多源最短路)。

算法描述:

一個十分暴力又經典的DP,假設i到j的路徑有兩種狀態:

①i和j直接有路徑相連:

②i和j間接聯通,中間有k號節點聯通:

假設dis[i][j]表示從i到j的最短路徑,對于存在的每個節點k,我們檢查一遍dis[i][k]+dis[k][j]。

?

//Floyd算法,時間復雜度:O(n^3)?

//Floyd算法,時間復雜度:O(n^3)?

int dis[MAXN][MAXN];

for(k=1;k<=n;k++)//枚舉?

{

? ? for(i=1;i<=n;i++)

? ? {

? ? ? ? for(j=1;j<=n;j++)

? ? ? ? {

? ? ? ? ? ? dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);//DP

? ? ? ? }

? ? }

}

【清北2019NOIP夏令營助你圓夢OI】

2.Dijkstra算法(無向圖,無負權邊):

算法描述:

多源最短路!

a.初始時,S只包含源點,即S={v},v的距離為0。U包含除v外的其他頂點,即:U={其余頂點},若v與U中頂點u有邊,則<u,v>正常有權值,若u不是v的出邊鄰接點,則<u,v>權值為∞。

b.從U中選取一個距離v最小的頂點k,把k,加入S中(該選定的距離就是v到k的最短路徑長度)。

c.以k為新考慮的中間點,修改U中各頂點的距離;若從源點v到頂點u的距離(經過頂點k)比原來距離(不經過頂點k)短,則修改頂點u的距離值,修改后的距離值的頂點k的距離加上邊上的權。

d.重復步驟b和c直到所有頂點都包含在S中。

啊~上面的的亂七八糟的概念太難懂了,還是舉個例子吧!如下圖!

?

我們假設1號節點為原點。

第一輪,我們可以算出2,3,4,5,6號節點到原點1的距離為[7,9,∞,∞,14],∞表示無窮大(節點間無法直接連通),取其中最小的7,就確定了1->1的最短路徑為0,1->2的最短路徑為7,同時取最短路徑最小的2節點為下一輪的前驅節點。

第二輪,取2節點為前驅節點,按照 前驅節點到原點的最短距離 + 新節點到前驅節點的距離 來計算新的最短距離,可以得到3,4,5,6號節點到原點1的距離為[17,22,∞,∞](新節點必須經過2號節點回到原點),這時候需要將新結果和上一輪計算的結果比較,3號節點:17>9,最短路徑仍然為9;4號節點:22<∞,更新4號節點的最短路徑為22,;5號節點:仍然不變為∞;6號節點:14<∞,更新6號節點的最短路徑為14。得到本輪的最短距離為[9,22,∞,14],1->3的最短路徑為9,同時取最短路徑最小的3節點為下一輪的前驅節點。

第三輪:同理上,以3號節點為前驅節點,可以得到4,5,6號節點到原點1的距離為[20,∞,11],根據最短路徑原則,和上一輪最短距離比較,刷新為[20,∞,11],1->3->6的最短路徑為11,同時取最短路徑最小的6節點為下一輪的前驅節點。

第四輪:同理,得到4,5號節點最短距離為[20,20],這兩個值相等,運算結束,到達這兩個點的最短距離都是20,如果這兩個值不相等,還要進行第五輪運算!

#include<cstdio>

#include<cstring>

const int N=100500;

const int M=200500;

int point[N]={0},to[M]={0},next[M]={0},len[M]={0},cc=0;

int dis[N];//最短路長度,dis[i]表示第i號點到源點(1號點)的最短距離

bool ever[N];//當前節點最短路有沒有確定?

int n,m;?

void AddEdge(int x,int y,int z)//添加新的邊和節點:x到y邊長z

{

? ? cc++;

? ? next[cc]=point[x];

? ? point[x]=cc;

? ? to[cc]=y;

? ? len[cc]=z;//len記錄x到y的邊長?

}

int main()

{

? ? int i,j,k;

? ? scanf("%d%d",&n,&m);

? ? for(i=1;i<=m;i++)

? ? {

? ? ? ? int a,b,c;

? ? ? ? scanf("%d%d%d",&a,&b,&c);

? ? ? ? AddEdge(a,b,c);//無向圖,要加兩遍?

? ? ? ? AddEdge(b,a,c);

? ? }

? ? memset(dis,0x3f,sizeof dis);//用極大值來初始化?

? ? dis[1]=0;//1號節點到自己最短距離為0?

? ? for(k=1;k<=n;k++)

? ? {

? ? ? ? int minp,minz=123456789;

? ? ? ? for(i=1;i<=n;i++)

? ? ? ? {

? ? ? ? ? ? if(!ever[i])

? ? ? ? ? ? {

? ? ? ? ? ? ? ? if(dis[i]<minz)

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? minz=dis[i];

? ? ? ? ? ? ? ? ? ? minp=i;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? ever[minp]=1;

? ? ? ? int now=point[minp];

? ? ? ? while(now)

? ? ? ? {

? ? ? ? ? ? int tox=to[now];

? ? ? ? ? ? if(dis[tox]>dis[minp]+len[now])

? ? ? ? ? ? dis[tox]=dis[minp]+len[now];

? ? ? ? ? ? now=next[now];

? ? ? ? }

? ? }

? ? for(i=1;i<=n;i++)

? ? ? ? printf("%d\n",dis[i]);

return 0;

【清北2019NOIP夏令營與你相隨OI之路】

?

3.SPFA算法(有負權邊,無負圈,能檢測負圈但不能輸出):

多源最短路!

SPFA和Dijkstra極為相似,只是加了個隊列優化來檢測負圈和負權邊。

算法描述:

建立一個隊列,初始時隊列里只有起始點,再建立一個表格記錄起始點到所有點的最短路徑(該表格的初始值要賦為極大值,該點到他本身的路徑賦為0)。然后執行松弛操作,用隊列里有的點作為起始點去刷新到所有點的最短路,如果刷新成功且被刷新點不在隊列中則把該點加入到隊列最后。重復執行直到隊列為空。

判斷有無負環:
如果某個點進入隊列的次數超過N次則存在負環(SPFA無法處理帶負環的圖)

#include<cstdio>

#include<cstring>

const int N=100500;

const int M=200500;

int point[N]={0},to[M]={0},next[M]={0},len[M]={0},cc=0;

int dis[N];//最短路長度

int queue[N],top,tail;//雙向隊列queue,隊頭,隊尾?

bool in[N];//記錄這個點在不在隊列中,1表示在,0表示不在?

int n,m; //n個節點,m條邊?

void AddEdge(int x,int y,int z)//x到y邊長為z?

{

? ? cc++;

? ? next[cc]=point[x];

? ? point[x]=cc;

? ? to[cc]=y;

? ? len[cc]=z;

}

int main()

{

? ? int i,j;

? ? scanf("%d%d",&n,&m);

? ? for(i=1;i<=m;i++)

? ? {

? ? ? ? int a,b,c;

? ? ? ? scanf("%d%d%d",&a,&b,&c);

? ? ? ? AddEdge(a,b,c);//因為是雙向隊列,左邊加一次,右邊加一次

? ? ? ? AddEdge(b,a,c);

? ? }

? ? memset(dis,0x3f,sizeof dis);//用極大值來初始化

? ? dis[1]=0;//1號節點到自己最短距離為0?

? ? top=0;tail=1;queue[1]=1;in[1]=1;//初始化,只有原點加入?

? ? while(top!=tail)

? ? {

? ? ? ? top++;

? ? ? ? top%=N;

? ? ? ? int now=queue[top];

? ? ? ? in[now]=0;

? ? ? ? int ed=point[now];

? ? ? ? while(ed)

? ? ? ? {

? ? ? ? ? ? int tox=to[ed];

? ? ? ? ? ? if(dis[tox]>dis[now]+len[ed])

? ? ? ? ? ? {

? ? ? ? ? ? ? ? dis[tox]=dis[now]+len[ed];

? ? ? ? ? ? ? ? if(!in[tox])

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? tail++;

? ? ? ? ? ? ? ? ? ? tail%=N;

? ? ? ? ? ? ? ? ? ? queue[tail]=tox;

? ? ? ? ? ? ? ? ? ? in[tox]=1;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? ed=next[ed];

? ? ? ? }

? ? }

? ? for(i=1;i<=n;i++)

? ? ? ? printf("%d\n",dis[i]);

? ? return 0;?

}

4.Bellman Ford算法(有負權邊,可能有負圈,能檢測負圈并輸出):

單源最短路!

算法描述:

1.初始化:將除源點外的所有頂點的最短距離估計值 d[all]=+∞, d[start]=0;

2.迭代求解:反復對邊集E中的每條邊進行松弛操作,使得頂點集V中的每個頂點v的最短距離估計值逐步逼近其最短距離;(運行|v|-1次)

3.檢驗負權回路:判斷邊集E中的每一條邊的兩個端點是否收斂。如果存在未收斂的頂點,則算法返回false,表明問題無解;否則算法返回true,并且從源點可達的頂點v的最短距離保存在 d[v]中。

簡單的說,如下圖所示:

?

松弛計算之前,點B的值是8,但是點A的值加上邊上的權重2,得到5,比點B的值(8)小,所以,點B的值減小為5。這個過程的意義是,找到了一條通向B點更短的路線,且該路線是先經過點A,然后通過權重為2的邊,到達點B

如果出現了以下情況:

?

松弛操作后,變為7,7>6,這樣就不修改(Bellman Frod算法的高妙之處就在這),保留原來的最短路徑就OK,代碼實現起來非常簡單。

int n,m;//n個點,m條邊?

struct Edge//定義圖類型結構體?

{

? ? int a,b,c;//a到b長度為c?

}edge[];

int dis[];

memset(dis,0x3f,sizeof dis);

dis[1]=0;

for(int i=1;i<n;i++)

{

? ? for(int j=1;j<=m;j++)

? ? {

? ? ? ? if(dis[edge[j].b]>dis[edge[j].a]+edge[j].c)

? ? ? ? {

? ? ? ? ? ? dis[edge[j].b]=dis[edge[j].a]+edge[j].c;? ? ? ??

? ? ? ? }

? ? }

}

5.A*算法:

這玩意兒我是沒看懂,等以后我看懂了再更吧(無奈臉)~

七、拓撲排序:

對一個有向無環圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是將G中所有頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊(u,v)∈E(G),則u在線性序列中出現在v之前。通常,這樣的線性序列稱為滿足拓撲次序(Topological Order)的序列,簡稱拓撲序列。

打個比喻:我們要做好一盤菜名字叫做紅燒茄子,那么第一步得買茄子和配料,第二步就是要洗茄子,第三步就是要開始倒油進鍋里啊什么七七八八的,第四步…,你不可能先洗茄子再買茄子和配料,這樣的一些事件必須是按照順序進行的,這些依次進行的事件就構成了一個拓撲序列。

算法描述:

我們需要一個棧或者隊列,兩者都可以無所謂,只是找個容器把入度為0的元素維護起來而已。

①從有向圖中選擇一個入度為0(無前驅)的頂點,輸出它。

②從網中刪去該節點,并且刪去從該節點出發的所有有向邊。

③重復以上兩步,直到剩余的網中不再存在沒有前驅的節點為止。

具體操作過程如下:

若棧非空,則在棧中彈出一個元素,然后枚舉這個點能到的每一個點將它的入度-1(刪去一條邊),如果入度=0,則壓入棧中。?
如果沒有輸出所有的頂點,則有向圖中一定存在環

//拓撲排序,時間復雜度:O(n+m)?

#include<cstdio>

#include<cstring>

const int N=100500;

const int M=200500;

int point[N]={0},to[M]={0},next[M]={0},cc=0;

int xu[N]={0};//棧,初始值為空,xu[0]表示棧的大小?

int in[N]={0};//入度,a可以到達b,in[b]++?

int ans[N]={0};//ans[0]整個拓撲序列的大小?

int n,m;?

void AddEdge(int x,int y)//鄰接表a到b?

{

? ? cc++;

? ? next[cc]=point[x];

? ? point[x]=cc;

? ? to[cc]=y;

}

int main()

{

? ? int i,j;

? ? scanf("%d%d",&n,&m);

? ? for(i=1;i<=m;i++)

? ? {

? ? ? ? int a,b;

? ? ? ? scanf("%d%d",&a,&b);

? ? ? ? in[b]++;//統計每個節點的入度?

? ? ? ? AddEdge(a,b);

? ? }

? ? for(i=1;i<=n;i++)

? ? {

? ? ? ? if(in[i]==0)//這個節點入度為0,壓入棧?

? ? ? ? xu[++xu[0]]=i;

? ? }

? ? while(xu[0])

? ? {

? ? ? ? int now=xu[xu[0]];//出棧?

? ? ? ? xu[0]--;

? ? ? ? ans[++ans[0]]=now;

? ? ? ? int ed=point[now];

? ? ? ? while(ed)

? ? ? ? {

? ? ? ? ? ? int tox=to[ed];

? ? ? ? ? ? in[tox]--;

? ? ? ? ? ? if(!in[tox])

? ? ? ? ? ? xu[++xu[0]]=tox;

? ? ? ? ? ? ed=next[ed];//找下一個相鄰節點?

? ? ? ? }

? ? }

? ? if(ans[0]<n)//有向圖中一定存在環,無結果?

? ? printf("no solution");

? ? else

? ? {

? ? ? ? for(i=1;i<=n;i++)

? ? ? ? printf("%d ",ans[i]);

? ? }

? ? return 0;

}

?

?

聯通分量:

強連通:有向圖中,從a能到b并且從b可以到a,那么a和b強連通。

強連通圖:有向圖中,任意一對點都滿足強連通,則這個圖被稱為強連通圖。

強聯通分量:有向圖中的極大強連通子圖,就是強連通分量。

一般用Tarjan算法求有向圖強連通分量:

?

歐拉路徑與哈密頓路徑:

1.歐拉路徑:從某點出發一筆畫遍歷每一條邊形成的路徑。

歐拉回路:在歐拉路徑的基礎上回到起點的路徑(從起點出發一筆畫遍歷每一條邊)。

歐拉路徑存在:

無向圖:當且僅當該圖所有頂點的度數為偶數 或者 除了兩個度數為奇數外其余的全是偶數。

有向圖:當且僅當該圖所有頂點 出度=入度 或者 一個頂點 出度=入度+1,另一個頂點 入度=出度+1,其他頂點 出度=入度

歐拉回路存在:

無向圖:每個頂點的度數都是偶數,則存在歐拉回路。

有向圖:每個頂點的入度都等于出度,則存在歐拉回路。

求歐拉路徑/歐拉回路算法常常用Fleury算法:

在這里推薦一個不錯的知乎作者:神秘OIer

2.哈密頓路徑:每個點恰好經過一次的路徑是哈密頓路徑。

哈密頓回路:起點與終點之間有邊相連的哈密頓路徑是哈密頓回路。

?

轉載于:https://www.cnblogs.com/qbxt/p/10981407.html

總結

以上是生活随笔為你收集整理的清北NOIP训练营集训笔记——图论(提高组精英班)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。