【2019.9.16】Za

//主要是根据各种网上资料做笔记

Floyd

\(f[i][j]\):从\(i\)号顶点到\(j\)号顶点只经过前\(k\)号点的最短路程

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\)为附加状态

应用

  1. 给一个正权无向图,找一个最小权值和的环

    这一定是一个简单环 考虑环上编号最大的结点\(u\)

    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

找最小环 并输出一个最小环方案

   #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跑差分约束==

因为砝码大小只有1、2、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

对比

FloydBellman-FordDijkstra
每对结点之间的最短路单源最短路单源最短路
无负环的图任意图非负权图
\(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;
}

转载于:https://www.cnblogs.com/lxyyyy/p/11526554.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值