【2019.9.16】Za

//主要是根據各類網上資料作筆記php

Floyd

\(f[i][j]\):從\(i\)號頂點到\(j\)號頂點只通過前\(k\)號點的最短路程html

for (k=1;k<=n;k++)
  for (i=1;i<=n;i++)
    for (j=1;j<=n;j++)
      f[i][j] = min(f[i][j],f[i][k]+f[k][j]);

\(k\)是階段 因此必須位於最外層 而\(i和j\)爲附加狀態node

應用

  1. 給一個正權無向圖,找一個最小權值和的環c++

    這必定是一個簡單環 考慮環上編號最大的結點\(u\)git

    f[u-1][x][y]\((u,x), (u,y)\)共同構成了環。算法

    在 Floyd 的過程當中枚舉\(u\),計算這個和的最小值便可數組

    有向圖的最小環問題 可枚舉起點\(s=1\sim n\) 執行對優化的\(Dijsktra\)求解單源最短路徑\(s\)必定爲第一個被從堆中取出節點 掃描\(s\)全部出邊 擴展、更新完成後 令\(d[s]=+\infty\) 而後繼續求解 當s第二次被從堆中取出時 \(d[s]\)就是通過點\(s\)的最小環長度閉包

POJ1734 sightseeing trip

找最小環 並輸出一個最小環方案app

#include<bits/stdc++.h>
   using namespace std;
   const int N=100+10,M=1e5+50,inf=0x3f3f3f3f;
   int n,m,mp[N][N],dis[N][N],pos[N][N];
   vector<int>path;
   void get_path(int x,int y){
    if(!pos[x][y]) return;
    get_path(x,pos[x][y]);
    path.push_back(pos[x][y]);
    get_path(pos[x][y],y);
   }
   
   int main(){
   //   freopen("in.txt","r",stdin);
    scanf("%d%d",&n,&m);
    int ans=inf;
    memset(mp,inf,sizeof(mp));
    for(int i=1;i<=n;++i) mp[i][i]=0;
    for(int i=1,x,y,w;i<=m;++i)
        scanf("%d%d%d",&x,&y,&w),mp[x][y]=mp[y][x]=w;
    memcpy(dis,mp,sizeof(mp));
    for(int k=1;k<=n;++k){
        for(int i=1;i<k;++i)
            for(int j=i+1;j<k;++j)
                if((long long)dis[i][j]+mp[i][k]+mp[k][j]<ans){
                    ans=dis[i][j]+mp[i][k]+mp[k][j];
                    path.clear(),path.push_back(i);
                    get_path(i,j);path.push_back(j),path.push_back(k);
                }
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
            if(dis[i][j]>dis[i][k]+dis[k][j]) dis[i][j]=dis[i][k]+dis[k][j],pos[i][j]=k;
    }
    if(ans==inf) return puts("No solution."),0;
    for(int i=0;i<path.size();++i) printf("%d ",path[i]);
    return 0;
   }
  1. 傳遞閉包優化

    已知一個有向圖中任意兩點之間是否有連邊,要求判斷任意兩點是否連通

    按照 Floyd 的過程,逐個加入點判斷一下。

    只是此時的邊的邊權變爲 \(1/0\),而取 \(min\)變成了運算。

    再進一步用 bitset 優化,複雜度能夠到 \(O\left(\dfrac{n^3}w\right)\)

for (k=1;k<=n;k++)
     for (i=1;i<=n;i++)
       for (j=1;j<=n;j++)
         f[i][j]|=f[i][k]&f[k][j];
// std::bitset<SIZE> f[SIZE];
   for (k = 1; k <= n; k++)
     for (i = 1; i <= n; i++)
       if (f[i][k]) f[i] = f[i] & f[k];

POJ1094 sorting it all out

給定 n 個變量,m 個不等式。

不等式之間具備傳遞性,即若 A>B 且 B>C ,則 A>C。

判斷這 m 個不等式是否有矛盾。

若存在矛盾,則求出\(T\)的最小值,知足僅用前\(T\)個不等式就能肯定不等式之間存在矛盾。

若無矛盾,則判斷這 m 個不等式是否能肯定每一對變量之間的關係。

若能,則求出\(T\)的最小值,知足僅用前\(T\)個不等式就能肯定每一對變量之間的大小關係。

==不得不說 lyd勞斯的代碼真的很好 精簡高效

\(mp[i][j]\)表示\(i<j\)

#include<bits/stdc++.h>
using namespace std;
const int N=100+10,M=1e5+50,inf=0x3f3f3f3f;
int n,m,x,y,mp[N][N],ok1,ok2;
char opt[10];
int main(){
    freopen("in.txt","r",stdin);
    //freopen("and.out","w",stdout);
    while(~scanf("%d%d",&n,&m)&&(n+m)){
        ok1=ok2=0;
        memset(mp,0,sizeof(mp));
        int i;
        for(i=1;i<=n;++i) mp[i][i]=1;
        for(i=1;i<=m;++i){
            scanf("%s",opt),ok2=0;
            x=opt[0]-'A'+1,y=opt[2]-'A'+1,mp[x][y]=1;
            for(int j=1;j<=n;++j)
                for(int k=1;k<=n;++k)
                    mp[j][k]|=mp[j][x]&mp[y][k];
            for(int j=1;j<n;++j)
                for(int k=j+1;k<=n;++k)
                    if(mp[j][k]&mp[k][j]){
                        printf("Inconsistency found after %d relations.\n",i);
                        j=n,ok1=1;break;
                    }
                    else if(!(mp[j][k]|mp[k][j])) ok2=1;
            if(ok1) break;
            if(!ok2){
                printf("Sorted sequence determined after %d relations: ",i);
                int a[N];
                memset(a,0,sizeof(a)) ;
                for(int j=1;j<=n;++j)
                for(int k=j+1;k<=n;++k)
                if(mp[j][k]) ++a[k];else ++a[j];
                for(int j=0;j<n;++j)
                for(int k=1;k<=n;++k)
                if(a[k]==j) {putchar(k+'A'-1);break;}
                puts(".");break;
            }
        }
        if(i>m) puts("Sorted sequence cannot be determined.");
        for(++i;i<=m;++i) scanf("%s",opt);
    }
    return 0;
}
  1. else

POJ2613 cow relays

給定一張由T條邊構成的無向圖,點的編號爲1~1000之間的整數。

求從起點S到終點E剛好通過N條邊(能夠重複通過)的最短路。

矩陣乘法和floyd的組合!

#include<bits/stdc++.h>
   using namespace std;
   const int N=200+10,M=1e6+10,inf=0x3f3f3f3f;
   int n,m,s,t,vc[M];
   struct mar{
    int a[N][N];
    mar operator *(mar &X){
        mar c;
        memset(c.a,inf,sizeof(c.a));
        for(int i=1;i<=vc[0];++i)
            for(int j=1;j<=vc[0];++j)
                for(int k=1;k<=vc[0];++k)
                c.a[i][j]=min(c.a[i][j],a[i][k]+X.a[k][j]);
        return c;
    }
   }ans,mp; 
   
   void qpow(mar a,int b){
    ans=a,--b;
    while(b){
        if(b&1) ans=ans*a;
        a=a*a,b>>=1;
    }
   }
   
   int main(){
   //   freopen("in.txt","r",stdin);
    scanf("%d%d%d%d",&n,&m,&s,&t);
    memset(mp.a,inf,sizeof(mp.a));
    for(int i=1,x,y,w;i<=m;++i){
        scanf("%d%d%d",&w,&x,&y);
        x=(!vc[x]?(vc[x]=++vc[0]):vc[x]),
        y=(!vc[y]?(vc[y]=++vc[0]):vc[y]);
        mp.a[x][y]=mp.a[y][x]=min(w,mp.a[x][y]);
    }
    //for(int i=1;i<=vc[0];++i) mp.a[i][i]=0;
    qpow(mp,n);
    printf("%d",ans.a[vc[s]][vc[t]]);
    return 0;
   }

SCOI2008 天平

bzoj1077 luogu2447

用floyd跑差分約束==

由於砝碼大小隻有一、二、3 因此未知時最大差值爲2 最小差值爲-2

\(A+B>C+D\)能夠轉爲\(A-C>D-B\) 而後就挨個判斷就行了

注意判斷等於時的條件

#include<bits/stdc++.h>
   using namespace std;
   #define Max(x,y) ((x)>(y)?(x):(y))
   #define Min(x,y) ((x)<(y)?(x):(y))
   const int N=50+10,M=1e6+10,inf=0x3f3f3f3f;
   int n,A,B,c1,c2,c3,mx[N][N],mn[N][N];
   char opt[N];
   
   int main(){
    freopen("in.txt","r",stdin);
    scanf("%d%d%d",&n,&A,&B);
    for(int i=1;i<=n;++i){
        scanf("%s",opt+1);mx[i][i]=mn[i][i]=0;
        for(int j=1;j<=n;++j)
        if(j!=i){
            if(opt[j]=='-') mn[i][j]=-2,mx[i][j]=-1;
            else if(opt[j]=='+') mn[i][j]=1,mx[i][j]=2;
            else if(opt[j]=='=') mx[i][j]=mn[i][j]=0;
            else mn[i][j]=-2,mx[i][j]=2;
        }
    }
    for(int k=1;k<=n;++k)
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
            mn[i][j]=Max(mn[i][j],mn[i][k]+mn[k][j]),
            mx[i][j]=Min(mx[i][j],mx[i][k]+mx[k][j]);
    for(int i=1;i<=n;++i)
    if(i!=A&&i!=B)
    for(int j=i+1;j<=n;++j)
    if(j!=A&&j!=B){
        if(mn[A][i]>mx[j][B]||mn[B][i]>mx[j][A]) ++c1;
        if(mn[i][A]>mx[B][j]||mn[i][B]>mx[A][j]) ++c3;
        if(mn[A][i]==mx[A][i]&&mn[j][B]==mx[j][B]&&mn[A][i]==mn[j][B])++c2;
                   else if(mn[B][i]==mx[B][i]&&mn[j][A]==mx[j][A]&&mn[B][i]==mn[j][A])++c2;
    }
    printf("%d %d %d",c1,c2,c3);
    return 0;
   }

SPFA

==就不說了

SPFA 的時間複雜度爲\(O(kM)(k\approx2)\)玄學),但理論上界\(O(NM)\)

SPFA 的優化之SLF即 Small Label First。

即在新元素加入隊列時,若是隊首元素權值大於新元素權值,那麼就把新元素加入隊首,不然依然加入隊尾。

該優化在確實在一些圖上有顯著效果,其複雜度也有保證,可是若是有負權邊的話,能夠直接卡到指數級。

Dijkstra

若是有向圖的邊權值全爲正數,那麼有一種複雜度有保證的單源最段路
算法——\(Dijkstra\) 算法 它的複雜度是\(O(|E| log |V|)\)
事實上,\(Dijkstra\) 算法的思想和 \(Prim\) 有不少相似之處 \(Dijkstra\) 算法
維護了一個未訪問的結點集合\(T\)以及一個從\(s\)到結點\(u\)的當前距離 \(dist[u]\)

實現

主要思想是,將結點分紅兩個集合已肯定最短路長度的,未肯定的 一開始第一個集合裏只有\(S\)

  1. 將除源外全部結點當前距離設置爲$ ∞\(,將源\)s$的當前距離設置爲\(0\)
    將當前節點設置爲源\(s\)
  2. 從當前結點\(u\)開始,找出全部在未訪問集合\(T\)中與\(u\)有邊\((u,v)\)的結
    \(v\)。若是\(dist[u]+w[u][v]\)
  3. 將當前節點從\(T\)中刪除,而且找到在\(T\)\(dist\)最小的結點設置爲新的
    當前節點。
  4. 重複\((2)\)\((3)\)直到\(T\)成爲空集。

時間複雜度:只用分析集合操做, 次 delete-min , 次 decrease-key

若是用暴力:\(O(n^2+m)\) 若是用堆 \(O((n+m)log\ n)\)

若是用 priority_queue:\(O((n+m)log\ m)\) (注:若是使用 priority_queue,沒法刪除某一箇舊的結點,只能插入一個權值更小的編號相同結點,這樣操做致使堆中元素是 \(O(m)\)的)

若是用線段樹(ZKW 線段樹): \((O(n+m)log\ n)\)

若是用 Fibonacci 堆:\(O(nlog\ n+m)\)(這就是爲啥優秀了)。

正確性

它的正確性在於,在未訪問集合\(T\)中結點的\(dist\)是從\(s\)開始通過已經
訪問集合中的結點到達它的最短路
若是選出的當前結點\(u\)\(dist\)不是最終的最小值,那麼它最終的最短
路必定是要通過一個此時\(T\)中的其它結點再到\(u\)。這時那個結點的\(dist\)
定要小於\(u\)\(dist\),這就和\(u\)\(dist\)最小的結點矛盾了!

輸出方案

開一個pre數組,在更新距離的時候記錄下來後面的點是如何轉移過去的,算法結束前再遞歸地輸出路徑便可。

好比 Floyd 就要記錄pre[i][j] = k;,Bellman-Ford 和 Dijkstra 通常記錄 pre[v] = u

對比

Floyd Bellman-Ford Dijkstra
每對結點之間的最短路 單源最短路 單源最短路
無負環的圖 任意圖 非負權圖
\(O(N^3)\) \(O ( NM )\) \(O((N+M)log\ M)\)

P3403 跳樓機

P3403 跳樓機

通過改造,srwudi的跳樓機能夠採用如下四種方式移動:

向上移動x層;向上移動y層;向上移動z層;回到第一層。

一個月黑風高的大中午,DJL來到了srwudi的家,如今他在srwudi家的第一層,碰巧跳樓機也在第一層。DJL想知道,他能夠乘坐跳樓機前往的樓層數。

yyb:先只考慮只用\(y,z\)兩種移動方式,它們必定可以到達一些樓層,
那麼這些樓層再只用\(x\)拓展就可以計算答案。
那麼咱們這樣子計算答案,設\(dis[i]\)表示能夠到達\(mod\ x=i\)樓層的最小值,

很巧妙!同餘最短路

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=100000+10,M=100000+10,inf=0x3f3f3f3f;
int x,y,z;
ll n,ans=0;
template <class t>void rd(t &x){
    x=0;int w=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x=w?-x:x;
}

int head[N],tot=0;
struct edge{int v,w,nxt;}e[M<<2];
void add(int u,int v,int w){
    e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}

queue<int>q;bool vis[N];
ll dis[N];
void spfa(){
    memset(dis,inf,sizeof(dis));
    memset(vis,0,sizeof(vis));
    q.push(1),vis[1]=1,dis[1]=1;
    while(!q.empty()){
        int u=q.front();q.pop(),vis[u]=0;
        for(int i=head[u],v,w;i;i=e[i].nxt)
        if(dis[v=e[i].v]>dis[u]+(w=e[i].w)){
            dis[v]=dis[u]+w;
            if(!vis[v]) q.push(v),vis[v]=1;
        }
    }
}

int main(){
//  freopen("in.txt","r",stdin);
    rd(n),rd(x),rd(y),rd(z);
    if(n==1||x==1||y==1||z==1) return printf("%lld",n),0;
    for(int i=0;i<x;++i) add(i,(i+y)%x,y),add(i,(i+z)%x,z);
    spfa();
    for(int i=0;i<x;++i)
        if(dis[i]<=n) ans+=(n-dis[i])/x+1;
    printf("%lld",ans);
    return 0;
}

[國家集訓隊]墨墨的等式

和跳樓機那題是同樣的==

#include<bits/stdc++.h>
using namespace std;
#define Max(x,y) ((x)>(y)?(x):(y))
#define Min(x,y) ((x)<(y)?(x):(y))
#define ll long long
const int N=12+10,M=500000+10,inf=0x3f3f3f3f;
int n,a[N];
ll b1,b2,ans=0;
template <class t>void rd(t &x){
    x=0;int w=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x=w?-x:x;
}

int head[M],tot=0;
struct edge{int v,w,nxt;}e[M*20];
void add(int u,int v,int w){
    e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}

queue<int>q;bool vis[M];
ll dis[M];
void spfa(){
    memset(dis,inf,sizeof(dis));
    memset(vis,0,sizeof(vis));
    q.push(0),vis[0]=1,dis[0]=0;
    while(!q.empty()){
        int u=q.front();q.pop(),vis[u]=0;
        for(int i=head[u],v,w;i;i=e[i].nxt)
        if(dis[v=e[i].v]>dis[u]+(w=e[i].w)){
            dis[v]=dis[u]+w;
            if(!vis[v]) q.push(v),vis[v]=1;
        }
    }
}

int main(){
    freopen("in.txt","r",stdin);
    rd(n),rd(b1),rd(b2);
    for(int i=1;i<=n;++i) rd(a[i]);
    sort(a+1,a+n+1);
    for(int i=0;i<a[1];++i)
        for(int j=2;j<=n;++j) add(i,(i+a[j])%a[1],a[j]);
    spfa();
    for(int i=0;i<a[1];++i){
        if(dis[i]<=b1-1) ans-=(b1-1-dis[i])/a[1]+1;
        if(dis[i]<=b2) ans+=(b2-dis[i])/a[1]+1;
    }
    printf("%lld",ans);
    return 0;
}

[USACO08JAN]電話線Telephone Lines

  • 法一:二分+01BFS
    很好想der 二分路徑中邊權從小到大排第k條的值 而後BFS時 不超過mid的邊權爲0 超過mid的爲1

    二分+01BFS

  • 法二:分層最短路

    #include<bits/stdc++.h>
    using namespace std;
    #define Max(x,y) ((x)>(y)?(x):(y))
    #define Min(x,y) ((x)<(y)?(x):(y))
    const int N=1000+10,M=10000+10,inf=0x3f3f3f3f;
    int n,m,K;
    template <class t>void rd(t &x){
        x=0;int w=0;char ch=0;
        while(!isdigit(ch)) w|=ch=='-',ch=getchar();
        while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
        x=w?-x:x;
    }
    
    int head[N],tot=0;
    struct edge{int v,w,nxt;}e[M<<1];
    void add(int u,int v,int w){
      e[++tot]=(edge){v,w,head[u]},head[u]=tot;
    }
    
    int dis[N][N];bool vis[N][N];
    struct node{int id,us;};
    queue<node>q;
    void spfa(){
      memset(dis,inf,sizeof(dis));
      memset(vis,0,sizeof(vis));
      q.push((node){1,0}),vis[1][0]=1,dis[1][0]=0;
      while(!q.empty()){
          node nw=q.front();q.pop();
          int u=nw.id,us=nw.us;vis[u][us]=0;
          for(int i=head[u],v,w;i;i=e[i].nxt){
              if(dis[v=e[i].v][us]>max(dis[u][us],(w=e[i].w))){//不用 
                  dis[v][us]=max(dis[u][us],(w=e[i].w));
                  if(!vis[v][us]) q.push((node){v,us}),vis[v][us]=1;
              }
              if(us<K&&dis[v][us+1]>dis[u][us]){
                  dis[v][us+1]=dis[u][us];
                  if(!vis[v][us+1]) q.push((node){v,us+1}),vis[v][us+1]=1;
              }
          }
      }
    }
    
    int main(){
      freopen("in.txt","r",stdin);
      rd(n),rd(m),rd(K);
      for(int i=1,u,v,w;i<=m;++i) rd(u),rd(v),rd(w),add(u,v,w),add(v,u,w);
      spfa();
      if(dis[n][K]==inf) return puts("-1"),0;
      printf("%d",dis[n][K]);
      return 0;
    }

    [CQOI2005]新年好

    重慶城裏有 \(n\)個車站,\(m\)條雙向公路鏈接其中的某些車站。每兩個車站最多用一條公路鏈接,從任何一個車站出發均可以通過一條或者多條公路到達其餘車站,但不一樣的路徑須要花費的時間可能不一樣。在一條路徑上花費的時間等於路徑上全部公路須要的時間之和。

    佳佳的家在車站 ,他有五個親戚,分別住在車站\(a,b,c,d,e\)。過年了,他須要從本身的家出發,拜訪每一個親戚(順序任意),給他們送去節日的祝福。怎樣走,才須要最少的時間?

    ==分別dij求出\(1,a,b,c,d\)點到各點的最短路 而後枚舉順序

    我用的\(next\_permutation\)來搞全排列==

#include<bits/stdc++.h>
using namespace std;
#define Max(x,y) ((x)>(y)?(x):(y))
#define Min(x,y) ((x)<(y)?(x):(y))
#define ll long long
typedef pair<int,int>pii;
const int N=50000+10,M=1e5+10,inf=0x3f3f3f3f;
int n,m,a[10],ans=inf,nw=0;
int b[10]={0,1,2,3,4,5};
template <class t>void rd(t &x){
    x=0;int w=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x=w?-x:x;
}

int head[N],tot=0;
struct edge{int v,w,nxt;}e[M<<1];
void add(int u,int v,int w){
    e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}

int dis[7][N];bool vis[N];
priority_queue<pii,vector<pii>,greater<pii> >q;
void dij(int id,int s){
    memset(vis,0,sizeof(vis));
    dis[id][s]=0,q.push(make_pair(0,s));
    while(!q.empty()){
        int u=q.top().second;q.pop();
        if(vis[u]) continue;vis[u]=1;
        for(int i=head[u],v,w;i;i=e[i].nxt){
            if(dis[id][v=e[i].v]>dis[id][u]+(w=e[i].w)){
                dis[id][v]=dis[id][u]+w;
                q.push(make_pair(dis[id][v],v));
            }
        }
    }
}

void cal(int id){
    nw+=dis[b[id-1]][a[b[id]]];
    if(id==5){ans=Min(ans,nw),nw=0;return;}
    cal(id+1);
}

int main(){
    freopen("in.txt","r",stdin);
    rd(n),rd(m);
    for(int i=1;i<=5;++i) rd(a[i]);a[0]=1;
    for(int i=1,u,v,w;i<=m;++i) rd(u),rd(v),rd(w),add(u,v,w),add(v,u,w);
    memset(dis,inf,sizeof(dis));
    for(int i=0;i<=5;++i) dij(i,a[i]);
    cal(1);
    while(next_permutation(b+1,b+6))
    cal(1);
    printf("%d",ans);
    return 0;
}
相關文章
相關標籤/搜索