zoukankan      html  css  js  c++  java
  • 【算法】图论

    【网络流与二分图】专题链接

    【图论】

    图论-刘汝佳

    完全三部图:图G可被分为三个顶点集,点集内的点相互均没有连边,不同点集的点之间相互均有连边。完全三部图的三元环个数是三点集点数的乘积。

    无向无环图就是树。有向无环图DAG方便操作。

    有环图可以tarjan缩点。

    哈密顿回路(路径):每个点只经过一次的回路,此时显然每条边也只经过一次,是NPC难题。

    欧拉回路(一笔画):每条边只经过一次的回路。

    【无向图存在欧拉路径】所有点度数均为偶数,或只有两个点的度数是奇数。

    【无向图存在欧拉回路】所有点度数均为偶数。

    【有向图存在欧拉回路】所有点入度=出度。

    欧几里得距离:两个点之间的距离,n维空间中的欧式距离的计算公式为:

    曼哈顿距离:两个点在标准坐标系上的绝对轴距总和,在2维空间中的计算公式为:

    (摘自欧几里得距离、曼哈顿距离和切比雪夫距离

    【最短路】Dijkstra+Heap比SPFA稳得多!

    floyd O(n3)

    SPFA 虽然理论上界O(nm),但是实际运行效果不错。

    SLF优化:设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾。

    循环队列大小至少开到点数那么大。

    bool spfa()
    {
        memset(vis,0,sizeof(vis));
        memset(d,0x3f,sizeof(d));
        int head=0,tail=1;q[0]=S;vis[S]=1;d[S]=0;
        while(head!=tail)
         {
             int x=q[head++];if(head>3000)head=0;
             for(int i=first[x];i;i=e[i].from)
              if(d[x]+e[i].w<d[e[i].v])
               {
                   int y=e[i].v;
                   d[y]=d[x]+e[i].w;
                   if(!vis[y])
                    {
                        if(d[y]<d[q[head]]){head--;if(head<0)head=3000;q[head]=y;}
                         else{q[tail++]=y;if(tail>3000)tail=0;}
                        vis[y]=1;
                    }
               }
             vis[x]=0;
         }
        return d[T];
    }
    SPFA+SLF优化

    spfa判负环(SLF把负权边放队头,优化显著):进队次数>n就有负环,或开数组记录步数(最短路路径条数),步数>n即有负环(两种方法一起用保证速度)

    退出来后不一定是环上的点,但一定受环的影响才会走多次,所以沿着pre并记录vis直到找到重复访问即读出了环。

    二分+DFS版SPFA判负环【BZOJ】1690: [Usaco2007 Dec]奶牛的旅行(玄学复杂度,速度优秀)

    dijkstra+Heap 理论上界O(mlog n),一般会十分接近上界,但是很稳

    只适用于无负权边的图。

    类似BFS,每次找到当前距离最近的点(即不会再被更新)进行更新,这样每个点只会被更新一次(但是堆中的点在被访问前会被频繁修改)

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int maxn=100010,maxm=500010,inf=0x3f3f3f3f;
    struct edge{int from,v,w;}e[maxm];
    struct Node{
        int x,d;
        bool operator <(const Node &b)const{
            return d>b.d;
        }
    }cyc;
    int n,m,first[maxn],tot,d[maxn],s,t;
    priority_queue<Node>q;
    void insert(int u,int v,int w)
    {tot++;e[tot].v=v;e[tot].w=w;e[tot].from=first[u];first[u]=tot;}
    int dijkstra()
    {
        memset(d,0x3f,sizeof(d));
        d[s]=0;q.push((Node){s,d[s]});
        while(!q.empty())
         {
            cyc=q.top();q.pop();
            if(cyc.d!=d[cyc.x])continue;
            int x=cyc.x;
            for(int i=first[x];i;i=e[i].from)
             if(d[e[i].v]>d[x]+e[i].w)
              {
                  d[e[i].v]=d[x]+e[i].w;
                  q.push((Node){e[i].v,d[e[i].v]});
              }
         }
        return d[t];
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
         {
             int u,v,w;
             scanf("%d%d%d",&u,&v,&w);
             insert(u,v,w);
             insert(v,u,w);
         }
        s=1;t=n;
        printf("%d",dijkstra());
        return 0; 
    }
    View Code

    技巧:

    ★dijkstra和spfa可以求多源最短路,只要d[x]=0就是起点集,然后对于每个点x,d[x]min就是到起点集的最短路。

    1.分别从S、T做一遍最短路,然后枚举边+两端最短路。

    2.求路径可以用DAG上的DP做。

    3.求最短路树:d[i]==d[j]+w(i,j)时为最短路树的边,在最短路中记录父亲就可以顺便求出(通常不管相同长度路径,建成树方便后续操作),这样树上有n-1条边。

    4.注意重边和自环。

    5.所有边反向重做。

    6.有时说不定可以走回头路。

    7.求最短/最长回路可以把所有指向S的边复制一份指向新的点S',然后跑最短/长路即可。

    8.判断是否在最短路上的常见套路:判断两端最短路(正反向)+边权是否等于整体最短路。如“第二短路”。

    最短路算法都是一样的,关键在建图

    明确状态表示(每个状态是一个点)

    编号:int& x=id[...];if(x==0)x==++n;

    预处理cango(能否走这一步),处理原点决策,处理每点的决策(明确状态表示,double表示已经加倍过了),跑最短路,处理终点。

    9.平面图转对偶图:浅析最大最小定理在信息学竞赛中的应用

    蓝书(《算法竞赛入门经典训练指南》)最短路最后一题。

    就是对于s-t平面图(边不相交,s,t都在外界面的边界上),可以从s-t拉一条边,那么以s-t内的边界边为起点,以s-t外的边界边为终点跑最短路就可以得到最小割。

    【差分约束】线性不等式组与图论最短路联系的桥梁

    参考资料:《浅析差分约束系统》    夜深人静写算法(四) - 差分约束

    约束条件:不等式组左侧为同数组两点差,右侧为常数。(约束条件一定要考虑全面细致)

    转化模型:对于约束条件xj-xi≤bk(j和i之间的最短路<=连边),新建边i--->j,权值为bk。加入一个源点s,从s出发与所有点相连,权值为0,跑最短路。

    1.如果要求两个变量差的最大值,那么将所有不等式转变成"<="的形式(极端约束条件,要尽可能大但必须≤...),则最短路就是满足所有约束的最大值

    2.相反,如果需要求的是两个变量差的最小值,那么需要将所有不等式转化成">=",建图后求最长路

    最短路:"<=的最短路"和">=的最长路“就是在现有约束下得出对于起点和终点的直接约束(搭一条边),即x[T]-x[S]≤d,所以d就是满足所有约束的最值

    解:<有解>  <无解:存在负环,d无穷小>  <无穷多解:s和t不连通,d无穷大,没有约束>

    两边夹定理:A-B=C  <=>  A-B>=C&&A-B<=C

    设num[ i ]为i时刻能够开始工作的人数,x[ i ]为实际雇佣的人数,那么x[ I ]<=num[ I ]。 
    设r[ i ]为i时刻至少需要工作的人数,于是有如下关系: 
        x[ I-7 ]+x[ I-6 ]+x[ I-5 ]+x[ I-4 ]+x[ I-3 ]+x[ I-2 ]+x[ I-1 ]+x[ I ]>=r[ I ] 
    设s[ I ]=x[ 1 ]+x[ 2 ]…+x[ I ],得到 
        0<=s[ I ]-s[ I-1 ]<=num[ I ], 0<=I<=23 
        s[ I ]-s[ I-8 ]>=r[ I ], 8<=I<=23 
        s[ 23 ]+s[ I ]-s[ I+16 ]>=r[ I ], 0<=I<=7
    
    这个问题比较特殊,我们还少了一个条件,即:s[23] = T,它并不是一个不等式,我们需要将它也转化成不等式,由于设定s[-1] = 0,所以 s[23] - s[-1] = T,它可以转化成两个不等式:
    
          s[23] - s[-1] >= T
          s[-1] - s[23] >= -T
    将这两条边补到原图中,求出的最长路s[23]等于T,表示T就是满足条件的一个解,由于T的值时从小到大枚举的(T的范围为0到N),所以第一个满足条件的解就是答案。
    zju1420

    一定要注意题目中的所有约束条件,例如结点编号顺序xi-xi-1>=0,千万不要漏掉。

    变量式出现三个变量时,考虑枚举其中一个变量。

    【最小生成树】MST

    【1】切割性质:如果某条边是跨越集合且边权最小的边,那么所有最小生成树一定经过它。(假设边权不等)

    理解:根据MST的定义,两个集合要连通必然是连最小的边,其中一个例子是每个点一定通过最小的邻边连接,MST不存在所谓决策!

    【2】回路性质:回路上边权最大的边,所有最小生成树一定不经过它。(假设边权不等)

    要连接回路上的n个点只需要n-1条边,因此最大的边一定不会连接,MST不存在所谓决策。

    【3】实现算法

    kruskal算法:根据切割性质,每次找到全图最小的跨集合边连接直到形成MST,复杂度O(m log m)。

    prim算法:维护构成MST的点集S,每次找到距离S最近的点加入点集直至形成MST,原理是最近的点不可能通过其它点中转使距离更小,需要邻接矩阵,O(n^2)。

    Boruvka算法:连接图上所有生成子树的最小邻边,然后再次进行直至完成,可证至多log n次,复杂度O(m log n)。

    常用kruskal算法。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int maxn=310;
    struct cyc{int from,to,pre,k;}e[100010];
    int fa[maxn],head[maxn],n,m,cnt,tot,maxs;
    bool cmp(cyc a,cyc b){return a.k<b.k;}
    void insert(int u,int v,int k)
    {cnt++;e[cnt].from=u;e[cnt].to=v;e[cnt].pre=head[u];head[u]=cnt;e[cnt].k=k;}
    int getfa(int x)
    {return fa[x]==x?x:(fa[x]=getfa(fa[x]));}
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
         {
             int u,v,c;
             scanf("%d%d%d",&u,&v,&c);
             insert(u,v,c);
             insert(v,u,c);
         }
        sort(e+1,e+cnt+1,cmp);
        for(int i=1;i<=n;i++)fa[i]=i;
        tot=0;
        for(int i=1;i<=cnt;i++)
         {
             int u=e[i].from,v=e[i].to;
             if(getfa(u)!=getfa(v))
              {
                  fa[fa[u]]=fa[v];
                  tot++;maxs=e[i].k;
              }
             if(tot>=n-1)break;
         }
        printf("%d %d",tot,maxs);
        return 0;
    }
    View Code

    应用:

    (1)增量最小生成树:每次加边构成一个环,根据回路性质,只需把原u,v路径上最大的一条边与新边比较即可。

    (2)最小瓶颈生成树:最小生成树一定是最小瓶颈生成树

    最小瓶颈路:起点和终点在最小生成树上的唯一路径就是最小瓶颈路。

    (3)次小生成树:根据枚举每条非MST边加入后构成的环的边交换代价,找到最小的即可。

    边交换实际上就是所有最小瓶颈路,预处理即可O(n^2)。

    (4)最小有向生成树(最小树形图):朱刘算法

    朱刘算法

    坑:《生成树的计数及其应用》

    【拓扑排序】不断寻找入度为0的点入队处理,处理时顺便把它能到达的点in-1,新产生的in=0入队。

    拓扑序可以保证DAG无后效性。若树边有向时可用于把递归(自上而下)改为拓扑+递推(自下而上),不过也可以BFS记录深度然后从最大深度往小扫,这样就保证叶子节点先扫到。

    有向图找简单环:拓扑排序后剩余环+环内DAG,DFS将DAG上的点(不能继续访问的点)在回溯时叉掉,然后访问到访问过的点就是环。

    【无向图的双连通分量】

    边双连通分量:任意两点之间至少存在两条边不重复的路径。要求每条边都至少在一个简单环中,即所有边都不是桥。

    点双连通分量:任意两点之间至少存在两条点不重复的路径。要求任意两条边都在同一个简单环中(一条边可能属于多个简单环),即内部无割顶。

    特别的,一条边相连的两个点视为一个点双

    一个图如果是点双连通分量(更严格),它也一定是边双连通分量(范围更大)。

    所以,双连通分量其实是一堆简单环组成的图,边双实际上是要求所有点和边都必须在环中,点双则更加严格。

    点双:一条边恰好属于一个点双连通分量,因为若属于两个,这两个分量间有两个公共点(边的两个端点),则实际上是同一个分量(两点保证了点不重复)。

    由此也可知两个点双间可能且至多有一个公共点,即割顶。反之任意割顶都是至少两个分量的公共点(点双tarjan算法的理论基础)

    边双:除了桥不属于任何边双连通分量之外,每条边恰好属于一个边双连通分量。两个分量之间不可能有公共点(否则为同一个),只能有桥。

    ★Tarjan算法(边双连通分量)

    tarjan算法是图连通问题的通用算法,其核心思想是找连通块断点

    令dfn[x]为x的时间戳,low[x]为x的往上走最远能到达的位置。

    未访问,low[x]=min(low[x],low[y])

    low[x]的值要根据x的子树的low值计算完毕后才可得知。

    已访问,low[x]=min(low[x],dfn[y])

    当检测到反向边(x,y)时,y一定是x的祖先(无向图中,若y不是x的祖先,则dfs到y时一定沿(x,y)先访问到x了,故不可能是横叉边)

    此时说明x能到达y的位置dfn[y](此时low[y]的值仍未得知,我们只需要先到达dfn[y],如果y能到更高,在回到y的时候再由low[y]去就行了)

    未访问,if(low[y]>dfn[x])iscut[i]=1

    找桥,在访问x的时候判断x-y是否桥。

    ★<边双连通分量>【POJ】3177 Redundant Paths

    边双连通分量更加常用。注意有重边时就不能禁止走向父亲而要禁止走原边。

    void tarjan(int x,int fa){
        dfn[x]=low[x]=++dfsnum;
        for(int i=first[x];i;i=e[i].from)if((i^1)!=fa){
            if(!dfn[e[i].v]){
                tarjan(e[i].v,i);
                low[x]=min(low[x],low[e[i].v]);
                if(low[e[i].v]>dfn[x])iscut[i]=iscut[i^1]=1;
            }else low[x]=min(low[x],dfn[e[i].v]);
        }
    }
    void dfs(int x){
        col[x]=colnum;
        for(int i=first[x];i;i=e[i].from)if(!col[e[i].v]&&!iscut[i])dfs(e[i].v);
    }
    View Code

    <点双连通分量>割顶low[y]>=dfn[x],且需要特殊处理根节点。

    割顶判断

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int maxn=100000,maxm=100000;
    struct edge{int u,v,from;}e[maxm*3];
    int first[maxn],mark,dfn[maxn],low[maxn],iscut[maxn],tot,n,m;
    void insert(int u,int v)
    {tot++;e[tot].v=v;e[tot].u=u;e[tot].from=first[u];first[u]=tot;}
    void dfs(int x,int fa)
    {
        dfn[x]=low[x]=++mark;
        int child=0;
        for(int i=first[x];i;i=e[i].from)
         if(e[i].v!=fa)
         {
             int y=e[i].v;
             if(!dfn[y])
              {
                  child++;//important
                  dfs(y,x);
                  low[x]=min(low[x],low[y]);
                  if(low[y]>=dfn[x])iscut[x]=1;
              }
             else /*if(dfn[y]<dfn[x])*/low[x]=min(low[x],dfn[y]);
         }
        if(fa<0&&child<=1)iscut[x]=0;
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
         {
             int u,v;
             scanf("%d%d",&u,&v);
             insert(u,v);insert(v,u);
         }
        for(int i=1;i<=n;i++)if(!dfn[i])dfs(i,-1);
        for(int i=1;i<=n;i++)if(iscut[i])printf("%d ",i);printf("
    ");
        return 0;
    }
    View Code

    点双连通分量

    #include<cstdio>
    #include<algorithm>
    #include<vector>
    #include<stack>
    #include<cstring>
    using namespace std;
    const int maxm=50010;
    struct edge{int u,v,from;}e[maxm*3];
    int first[maxm],iscut[maxm],dfn[maxm],low[maxm],bccno[maxm],bcc_cnt,dfsnum,tot,n,m,kase;
    vector<int>bcc[maxm];
    stack<int>s;
    void insert(int u,int v)
    {tot++;e[tot].u=u;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
    void tarjan(int x,int fa)
    {
        dfn[x]=low[x]=++dfsnum;
        int child=0;
        for(int i=first[x];i;i=e[i].from)
         if(e[i].v!=fa)
          {
              int y=e[i].v;
    //          s.push(i);
              if(!dfn[y])
               {
                  s.push(i);//
                  child++; //important
                   tarjan(y,x);
                   low[x]=min(low[x],low[y]);
                   if(low[y]>=dfn[x])
                    {
                        iscut[x]=1;
                        bcc_cnt++;
                        bcc[bcc_cnt].clear();
                        for(;;)
                         {
                             int now=s.top();s.pop();
                             int u=e[now].u,v=e[now].v;
                             if(bccno[u]!=bcc_cnt){bcc[bcc_cnt].push_back(u);bccno[u]=bcc_cnt;}
                             if(bccno[v]!=bcc_cnt){bcc[bcc_cnt].push_back(v);bccno[v]=bcc_cnt;}
    //                         printf("%d
    ",bcc_cnt);
                             if(u==x&&v==y)break;
                         }
                    }
               }
              else s.push(i),low[x]=min(low[x],dfn[y]);//important
          }
        if(fa<0&&child==1)iscut[x]=0;//根节点特判!!! 
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        while(m!=0)
         {
             memset(first,0,sizeof(first));
             tot=0;bcc_cnt=0;
             for(int i=1;i<=m;i++)
              {
                  int u,v;
                  scanf("%d%d",&u,&v);
                  insert(u,v);
                  insert(v,u);
              }
             memset(dfn,0,sizeof(dfn));
             memset(low,0,sizeof(low));
             memset(iscut,0,sizeof(iscut));
             memset(bccno,0,sizeof(bccno));
             dfsnum=0;
             while(!s.empty())s.pop();
             for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,-1);
             for(int i=1;i<=n;i++)printf("%d ",iscut[i]);printf("
    ");
             for(int i=1;i<=bcc_cnt;i++)
              {
                  printf("#%d: ",i);
                  for(int j=0;j<bcc[i].size();j++)
                   printf("%d ",bcc[i][j]);
                  printf("
    ");
              }
             scanf("%d%d",&n,&m);
         }
        return 0;
    }
    View Code

    缩点:简单环就是双连通分量,所以双连通缩点后就是树(无向无环连通图)。

    【有向图的强连通分量】

    定义有向图的极大连通子图(相互可达)为强连通分量(SCC)。

    经常将SCC缩点使图变为DAG(有向无环图)。(也就是极端情况下,每个点自成一个SCC)

    Tarjan算法(强连通分量)

    核心思想是通过dfn[x]=low[x]找到SCC。

    low[x]=min(low[x],low[y])

    传递

    if(!col[y])low[x]=min(low[x],dfn[y])

    阻止连向SCC,因为已经形成了的SCC不会再往外连边,此时往内连边没用且使用其dfn会导致后面和low判等出错。

    if(dfn[x]==low[x])

    DFS过程中用栈记录点,lack记录每个点在栈中的位置。

    一旦能形成SCC就出栈到lack[x],用col染色。

    补充:原理是一旦下面的点都能到达这里就形成SCC,而下面有一些不能到达的已经自成SCC了,是典型的子过程思想。

    缩点

    读每条边,当color不同时建新边,color就是天然的新图节点标号,留下来的信息只有点权(SCC内节点数量)。

    参考:https://www.byvoid.com/blog/scc-tarjan/

    http://www.cnblogs.com/saltless/archive/2010/11/08/1871430.html

    void tarjan(int x)
    {
        dfn[x]=low[x]=++mark;
        s[++top]=x;lack[x]=top;
        for(int i=first[x];i;i=e[i].from)
         {
             int y=e[i].v;
             if(!dfn[y])
              {
                  tarjan(y);
                  low[x]=min(low[x],low[y]);
              }
             else if(!col[y])low[x]=min(low[x],dfn[y]);
         }
        if(dfn[x]==low[x])
         {
             color++;
             for(int i=lack[x];i<=top;i++)col[s[i]]=color;
             num[color]=top-lack[x]+1;
             top=lack[x]-1;
         }
    }
    for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
    View Code

    经典问题:【BZOJ】2208 [Jsoi2010]连通数

    【无向连通图计数】带标号

    引用自:图论--连通图计数模板 by 奚政

    问题:含有n个不同的点的连通图有多少种(同构的图算多个,即带标号)。

    题解:

    记 含有n个点的连通图个数为 F[n] ;

      含有n个点的不连通图个数为 G[n] ;

      含有n个点的图的个数为 H[n] = 2^(n*(n-1)/2) ;

      有  F[n] + G[n] = H[n] ;  F[n] = H[n] - G[n] ;

    因此要求 F[n] 只需求 G[n] ;

    求 G[n] :枚举 1 号点所在的连通块大小为 k ( 1 ~ n-1 ) ;

         当 1 号点所在连通块大小为 k 时: k个点选取情况为组合数  C[n-1][k-1]

                         该连通块的种类数确定 k 个点后,连通块的种类数为 F[k] 

                         剩余 n-k 个点组成的图的种类数 H[n-k]

                         因此情况数为 C[n-1][k-1] * F[k] * H[n-k]

         G[n]为所有枚举的情况数总和  Σ( k : 1 ~ n-1 ) ( C[n-1][k-1] * F[k] * H[n-k] )

    定初始值 F[1] = 1 , G[1] = 1

    BZOJ3456 多项式求逆!

    【最短路顶点计数】

    题意:给定带权无向图,定义顶点为删去影响两两最短路的点,求顶点数。

    结论:以每个点为根做dijkstra,若某个点只有一条路径到达,则该点的前继为顶点,因为删去前继后会使根到该点断路。(不能将路径上的点都标识,只能标识前继)

    数据小所以可以用n^2的dijkstra。

    【2-sat】你以为你会2-sat了,其实你只知道怎么用……

    【描述】给定n个布尔变量,m个对两个变量的限制条件(与或非),求可行解,最小字典序解等问题。

    【建模】将第i个布尔变量拆成2*i-1和2*i两个点分别表示真和假,对限制条件连接“推导出”边。

    【算法】O(m)的判可行性的dfs算法:对每个未决策的点假设为假,然后dfs,遇到标记过却不符合返回矛盾。矛盾则假设为真再dfs,再矛盾则全图无解(不回溯)。若不矛盾则过程中标记的点都决策完毕。

    【原理】开始阐述2-sat算法原理。

    对称性:2-sat最重要的特点就是建模具有对称性,i->j'必然伴随j->i',因此点x如果被y强制选,点x'一定会强制选y‘

    连通块理论:为什么不用回溯?

    连通块:也就是一个变量确定决策后,该点能到的地方都确定了决策,我们把这些一起确定了决策的点称为一个连通块(为了方便)。

    假设连通块内点x决策为假,外界如果有边连向x假,这条边没有意义。外界如果有边连向x真,由对称性x假会连向外界,则连通块会拓展,故外界无边连向连通块内。

    综上,连通块十分独立,它已经和剩下未决策的点无关,故改变前面的决策也不会对结果产生影响,所以不用回溯,所以矛盾了就全图无解(改变之前的决策也没用)。

    矛盾判定理论:为什么真假都矛盾才算矛盾?

    由①可知连通块独立,所以一个点dfs不可能遇到之前由其它点确定决策的点。

    故一个点dfs遇到已标记的点只会是dfs中前后遇到,只有两种情况:

    两次遇到x,构成环,正常返回。

    两次遇到x和x’,矛盾。(可能是环,也可能不是)

    对于第二种情况,如果真假都是如此,就说明有环包含了x和x‘,则全图矛盾(从而推出2-sat的定理:无解当且仅当存在环包含x和x’)。

    不回溯dfs算法就由“连通块”和“矛盾判定”两部分构成,可以O(m)地求解2-sat问题,包括但不限于可行性、可行解、最小字典序解(按顺序安排)等。

    所以为什么网上流行缩点+拓排,没人看紫书吗……?

    另外有拓扑排序做法的一些资料:kuangbin 详细介绍 论文 对称性论文,以后发现了dfs做法的缺陷再来补。

    缺陷:虽然复杂度正确,但是有爆栈风险!解决方法是倒过来处理,或把处理序列随机化

    复杂度不对……不好意思,已经改写法了233

    经典问题:三元环计数

    1.暴力分块

    将点分成点度小于$sqrt m$的点和大于等于$sqrt m$的点。

    对于第一类点,暴力枚举每个点的两条边搭配,极限情况是√m个点,每个点由√m条边,复杂度为O(m√m)。

    对于第二类点,暴力枚举三个点判断是否三元环,复杂度O(m√m)。

    暴力分块的特点是:一类块多块小,针对块内容设计算法。一类块少块大,针对块数量设计算法。

    2.点度

    枚举每条边,枚举该边点度小的一个端点的邻接边来判断,复杂度O(m√m)。原因不明。

    3.定向

    每条边指向点度大的点,同点度指向编号小的点,这样一定是一个DAG,且每个点的出度<√n。

    然后枚举每条边,枚举度数小的点的出度,复杂度O(m√n)。

  • 相关阅读:
    JavaScript操作符instanceof揭秘
    Linux打开txt文件乱码的解决方法
    Working copy locked run svn cleanup not work
    poj 2299 UltraQuickSort 归并排序求解逆序对
    poj 2312 Battle City 优先队列+bfs 或 记忆化广搜
    poj2352 stars 树状数组
    poj 2286 The Rotation Game 迭代加深
    hdu 1800 Flying to the Mars
    poj 3038 Children of the Candy Corn bfs dfs
    hdu 1983 Kaitou Kid The Phantom Thief (2) DFS + BFS
  • 原文地址:https://www.cnblogs.com/onioncyc/p/6617915.html
Copyright © 2011-2022 走看看