培训补坑(day1:最短路two-sat)
經過12天的滾粗,終于迎來了暑期培訓的結尾啦QAQ
結業考才考了90分,真是對不起孫爺(孫爺請收下我的膝蓋)
orz小粉兔怒D rank 1 獲得小粉兔一只QAQ
由于這次12天的培訓題目又比較多,算法較難,所以只能等到課程結束之后再來補坑了。
現在開始補坑(BEGIN大工程!!)
day1主要知識:最短路+two-sat
首先是最短路:
對于最短路目前我們大部分人用的就是兩種算法:
一、dijkstra算法:
dij算法基于的思想就是每次從所有已拓展的節點相連的未拓展的節點取出一個dis值最小的節點加入集合,直到找到所有點的最短路
這種做法唯一的優點就是跑的比spfa快,但是這種算法不能處理負權邊。而且也只是處理單源最短路問題
對于找到dis最小值的點有兩種方法,第一種就是n^2暴力枚舉點,但是這樣太慢,所以通常我們把點放入優先隊列中或者是堆中優化復雜度
然后我們貼個圖來講解一下具體做法:
一開始的時候沒有一個點在集合中:
接下來一號節點入隊
我們發現4號節點離1號節點最近,4號節點入隊。
接下來我們發現3號節點離1號節點和4號節點最近,3號節點入隊
?
最后二號節點入隊
?
?
至此,最短路結束。
下面附上例題:
給定N個節點,M條無向邊的圖,求1號節點到N號節點次短路的長度
備注:次短路是長度大于最短路中最短的路徑
——————————————我是分割線——————————————
這道題目顯然是單源最短路問題,只不過我們我們找到一個點看能不能更新最短路,如果能就更新,否則再和次短路進行比較。注意這題最短路和次短路不能相同,所以如果一個點的dis值與最短路的值相同要跳過才可以。
下面附上代碼
#include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define MN 200005 #define M 5005 using namespace std; int head[M],d1[M],d2[M],n,m,num; struct e{int to,next,w; }g[MN]; void ins(int u,int v,int val){g[++num].next=head[u];head[u]=num;g[num].to=v;g[num].w=val;} void insw(int u,int v,int w){ins(u,v,w);ins(v,u,w);} void swap(int &a,int &b){a^=b;b^=a;a^=b;} struct edge{int to,value;edge(int to,int value):to(to),value(value) {}friend bool operator <(edge a,edge b){return a.value>b.value;} }; priority_queue<edge>q; void dij(){d1[1]=0;q.push(edge(1,0));while(!q.empty()){edge tmp=q.top();q.pop();if(tmp.value>d2[tmp.to])continue;for(int i=head[tmp.to];i;i=g[i].next){int dd=tmp.value+g[i].w;if(dd==d1[g[i].to])continue;if(dd<d1[g[i].to]){swap(dd,d1[g[i].to]);q.push(edge(g[i].to,d1[g[i].to]));}if(dd<d2[g[i].to]){swap(dd,d2[g[i].to]);q.push(edge(g[i].to,d2[g[i].to]));}}} } int main(){scanf("%d%d",&n,&m);memset(d1,0x3f,sizeof(d1));memset(d2,0x3f,sizeof(d2));int x,y,val;for(int i=1;i<=m;i++)scanf("%d%d%d",&x,&y,&val),insw(x,y,val);dij(),printf("%d\n",d2[n]);return 0; }二、spfa
spfa的原理與dij有點相像,不過它處理的是多元最短路問題,可以有多個起點,它的基礎算法就是松弛操作,只要目前在隊中的節點有一條邊能使到達其他點的dis值更小,就更新dis值,并且讓被更新的節點入隊,由于一個點最多入隊n次,所以最壞復雜度是O(nm),不過有一些優化可以讓這個算法變得更快,這個我們下面講。
還是剛才那張圖
首先1號節點入隊(假設只有一個源)
然后我們發現與一號節點相連的點有二號和四號節點。更新dis[2]=50,dis[4]=10,2,4號節點入隊。1號節點出對。
然后我們看到了三號節點沒有進過隊,它的dis值是inf于是3號節點入隊,2號節點出隊
?
我們發現4號節點能更新2號節點和3號節點,更新完后4號節點出隊
?
最后我們發現3號節點能夠更新2號節點,更新完3號節點出隊,最短路結束。
在附上代碼之前講2個優化。
1、SLF優化
因為我們看到上圖進行了多次松弛操作,所以復雜度較高。
不過如果我們調換一下入隊順序,比如原來的入隊順序是2 3 4節點
如果我們改成4 3 2節點,我們就會發現沒有松弛操作了。一次就得出了正解。
這就是SLF優化。如果當前找到的v(邊的終點)節點的dis值比隊頭元素的dis值更小,那么我們就把這個點放在隊頭。這樣能優化30%左右,并且在本圖中效果最為明顯。
?2、循環隊列優化
再講一個不是優化的優化,就是循環隊列。因為在一個隊列中最多只能同時存在n(n為點的總數)個元素,所以我們可以將隊列循環使用,節省空間。
下面直接附上代碼
void spfa(){memset(vis,0,sizeof(vis));for(int i=1;i<=n;i++)dis[i]=inf;dis[T]=0;vis[T]=1;int h=0,t=1;que[1]=T;while(h!=t){int tmp=que[(++h)%=MN];for(int i=head[tmp];i;i=g[i].next)if(g[i^1].w&&dis[g[i].to]-dis[tmp]+g[i].c>eps){dis[g[i].to]=dis[tmp]-g[i].c;if(!vis[g[i].to]){vis[g[i].to]=1;if(dis[g[i].to]<dis[que[(h+1)%MN]])que[h]=g[i].to,h=(h-1+MN)%MN;else que[(++t)%=MN]=g[i].to;}}vis[tmp]=0;} }接下來我們講講第二個大塊two-set
two-sat問題就是這樣:對于一個事件你只有兩種選擇,選,或者不選。
而且對于兩個事件間還有一些約束條件,如A、B中至少要選一個。
而two-sat算法就是將這些限制條件轉換為不同狀態之間的邊,然后通過染色來判斷滿足條件的情況是否存在的。
two-sat最重要的方法就是將一個點拆成2個,一個是選,一個是不選。
舉個例子:
假如A、B之間我至少要選一個
那么反之,如果我A沒選,那么B一定選
如果我B沒選,A我一定選
那么我只要在非A與B,非B與A之間連一條單向邊就好了。(非表示不選)
其他的條件同理
下面直接附上例題代碼(由于是英文大家就自己理解啦。。我偷懶一下下QAQ)
#include<cstdio> #include<cstring> #define MN 100005 using namespace std; int n,m,tot,x,y,num,top; int age[MN],vis[MN*2],stack[MN],head[MN*2]; struct edge{int to,next; }g[MN*8]; void ins(int u,int v){g[++num].next=head[u];head[u]=num;g[num].to=v;} bool kind(int now){return age[now]*n<tot;} bool dfs(int u){if(vis[u^1])return false;if(vis[u])return true;vis[u]=true;stack[top++]=u;for(int i=head[u];i;i=g[i].next){if(!dfs(g[i].to))return false;}return true; } bool t_sat(){memset(vis,0,sizeof(vis));for(int i=0;i<2*n;i+=2){if(vis[i]||vis[i^1])continue;top=0;if(!dfs(i)){while(top)vis[stack[--top]]=0;if(!dfs(i^1))return false;}}return true; } int main(){while(scanf("%d%d",&n,&m)!=EOF){if(n==0&&m==0)break;memset(g,0,sizeof(g));memset(head,0,sizeof(head));memset(stack,0,sizeof(stack));num=tot=0;for(int i=0;i<n;i++)scanf("%d",&age[i]),tot+=age[i];for(int i=1;i<=m;i++){scanf("%d%d",&x,&y);x--,y--;if(kind(x)==kind(y)){ins(x<<1,y<<1|1);ins(y<<1,x<<1|1);}ins(x<<1|1,y<<1);ins(y<<1|1,x<<1);}if(t_sat()){for(int i=0;i<2*n;i+=2){if(!vis[i]){printf("C\n");continue;}else if(kind(i/2))printf("B\n");else printf("A\n");}}else printf("No solution.\n");} }?
?
?
?
轉載于:https://www.cnblogs.com/ghostfly233/p/7159631.html
總結
以上是生活随笔為你收集整理的培训补坑(day1:最短路two-sat)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux 64位 CentOS下安装
- 下一篇: luogu P1007 独木桥