Gusfield算法学习

Posted 无心随笔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gusfield算法学习相关的知识,希望对你有一定的参考价值。

算法详解

等价流树正如其名,树上两点间的路径上的边权最小值为图上两点间的最小割。

Gusfield算法就是建等价流树的一种算法。设当前正在处理的集合为 \\(S(|S|\\ge 2)\\),从 \\(S\\) 中任选两个点 \\(x,y\\),求出 \\(x,y\\) 间的最小割也就是最大流 \\(flow\\),此时在最小割树中加入一条从 \\(x\\)\\(y\\),边权为 \\(flow\\)。在原图中去掉所有的满流边后,\\(S\\) 会变成两部分,一部分是在残量网络上从起点能遍历到的点,另一部分是遍历不到的点,分治下去求解即可,很容易可以发现,最多求解 \\(n-1\\) 次,所以得到的必然是棵树。

证明这个做法建出来的树必然满足树上路径边权最小值等于原图上两点间的最小割。

有一种不严谨的证明,因为递归出来的两边都是合法的等价流树,假设这两棵等价流树中的边权全部大于 \\((s,t)\\) 这条边,那么显然割掉 \\((s,t)\\) 代表的原图中的边集会优于割掉其它边代表的原图中的边集。即若存在 \\(u,v\\) 分别再 \\(s,t\\) 侧,\\((s,t)\\) 这条边的边权必然为 \\(u,v\\) 的最小割。至于更详细的证明可以研读2016年王文涛国集论文

例题

1.【模板】最小割树(Gomory-Hu Tree)

其实我并不觉得这是GHT的模板,它只用到了大小,并没有用到方案等其它信息。直接建出等价流树,每次询问在等价流树上找路径边权最小值即可,倍增预处理皆可。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO
    template<typename T>
    inline void read(T &x)
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>\'9\'||ch<\'0\')
            if(ch==\'-\')
                f=-1;
            
            ch=getchar();
        
        while(ch>=\'0\'&&ch<=\'9\')
            x=x*10+(ch-\'0\');
            ch=getchar();
        
        x=(f==1?x:-x);
    
    template<typename T>
    inline void write(T x)
        if(x<0)
            putchar(\'-\');
            x=-x;
        
        if(x>=10)
            write(x/10);
        
        putchar(x%10+\'0\');
    
    template<typename T>
    inline void write_endl(T x)
        write(x);
        putchar(\'\\n\');
    
    template<typename T>
    inline void write_space(T x)
        write(x);
        putchar(\' \');
    

using namespace IO;
const int N=510,M=6010,Lg=11,inf=1e9;
int n,m,head[N],hd[N],tot1=0,tot=1,id[N];
struct edge
    int v,w,nxt;
e[M],G[M];
void add(int u,int v,int w)
    e[++tot].v=v;
    e[tot].nxt=head[u];
    e[tot].w=w;
    head[u]=tot;

void add1(int u,int v,int w)
    G[++tot1].v=v;
    G[tot1].w=w;
    G[tot1].nxt=hd[u];
    hd[u]=tot1;

namespace GHT
    int dep[N],cur[N],tmp1[N],tmp2[N];
    bool bfs(int S,int T)
        for(int i=1;i<=n;i++)
            dep[i]=0;
        
        queue<int>q;
        q.push(S);
        dep[S]=1;
        while(!q.empty())
            int u=q.front();
            q.pop();
            for(int i=head[u];i;i=e[i].nxt)
                int v=e[i].v,w=e[i].w;
                if(w&&!dep[v])
                    dep[v]=dep[u]+1;
                    q.push(v);
                    if(v==T)
                        return 1;
                    
                
            
        
        return 0;
    
    int dfs(int u,int flow,int T)
        if(u==T)
            return flow;
        
        int s=0;
        for(int i=cur[u];i;i=e[i].nxt)
            cur[u]=i;
            int v=e[i].v,w=e[i].w;
            if(w&&dep[v]==dep[u]+1)
                int res=dfs(v,min(flow,w),T);
                e[i].w-=res;
                e[i^1].w+=res;
                flow-=res;
                s+=res;
            
            if(!flow)
                break;
            
        
        if(!s)
            dep[u]=0;
        
        return s;
    
    void init()
        for(int i=0;i<=tot;i+=2)
            e[i].w+=e[i^1].w;
            e[i^1].w=0;
        
    
    int dinic(int S,int T)
        init();
        int ans=0;
        while(bfs(S,T))
            memcpy(cur,head,sizeof(head));
            ans+=dfs(S,inf,T);
        
        return ans;
    
    void build(int l,int r)
        if(l>=r)
            return;
        
        int x=id[l],y=id[l+1];
        int flow=dinic(x,y);
        add1(x,y,flow);
        add1(y,x,flow);
        int cnt1=0,cnt2=0;
        for(int i=l;i<=r;i++)
            if(dep[id[i]])
                tmp1[++cnt1]=id[i];
            
            else
                tmp2[++cnt2]=id[i];
            
        
        for(int i=1;i<=cnt1;i++)
            id[l+i-1]=tmp1[i];
        
        for(int i=1;i<=cnt2;i++)
            id[cnt1+i+l-1]=tmp2[i];
        
        build(l,l+cnt1-1);
        build(l+cnt1,r);
    

int f[N][Lg+5],mn[N][Lg+5],dep[N];
void make_tree(int u,int father)
    f[u][0]=father;
    dep[u]=dep[father]+1;
    for(int i=1;i<=Lg;i++)
        f[u][i]=f[f[u][i-1]][i-1];
        mn[u][i]=min(mn[u][i-1],mn[f[u][i-1]][i-1]);
    
    for(int i=hd[u];i;i=G[i].nxt)
        int v=G[i].v,w=G[i].w;
        if(v==father)
            continue;
        
        mn[v][0]=w;
        make_tree(v,u);
    

int lca(int u,int v)
    if(dep[u]<dep[v])
        swap(u,v);
    
    int mini=inf;
    for(int i=Lg;i>=0;i--)
        if(dep[f[u][i]]>=dep[v])
            mini=min(mini,mn[u][i]);
            u=f[u][i];
        
    
    if(u==v)
        return mini;
    
    for(int i=Lg;i>=0;i--)
        if(f[u][i]!=f[v][i])
            mini=min(mini,min(mn[u][i],mn[v][i]));
            u=f[u][i],v=f[v][i];
        
    
    return min(mini,min(mn[u][0],mn[v][0]));

signed main()
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    memset(mn,0x3f,sizeof(mn));
    read(n),read(m);
    for(int i=1,u,v,w;i<=m;i++)
        read(u),read(v),read(w);
        add(u,v,w);
        add(v,u,0);
        add(v,u,w);
        add(u,v,0);
    
    for(int i=1;i<=n;i++)
        id[i]=i;
    
    GHT::build(1,n);
    make_tree(1,0);
    int q;
    read(q);
    while(q--)
        int u,v;
        read(u),read(v);
        write_endl(lca(u,v));
    
    return 0;

2.[ZJOI2011]最小割

也是个板题,建出等价流树,如果不连通,就每个连通块之间建一条长度为 \\(0\\) 的边,最后询问时在树上以每个点为起点扫一遍即可。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define int long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO
    template<typename T>
    inline void read(T &x)
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>\'9\'||ch<\'0\')
            if(ch==\'-\')
                f=-1;
            
            ch=getchar();
        
        while(ch>=\'0\'&&ch<=\'9\')
            x=x*10+(ch-\'0\');
            ch=getchar();
        
        x=(f==1?x:-x);
    
    template<typename T>
    inline void write(T x)
        if(x<0)
            putchar(\'-\');
            x=-x;
        
        if(x>=10)
            write(x/10);
        
        putchar(x%10+\'0\');
    
    template<typename T>
    inline void write_endl(T x)
        write(x);
        putchar(\'\\n\');
    
    template<typename T>
    inline void write_space(T x)
        write(x);
        putchar(\' \');
    

using namespace IO;
const int N=510,M=60010,inf=1e12;
int n,m,head[N],hd[N],tot1=0,tot=1,id[N],fa[N];
struct edge
    int v,w,nxt;
e[M],G[M];
int getfa(int x)
    if(fa[x]!=x)
        fa[x]=getfa(fa[x]);
    
    return fa[x];

void merge(int u,int v)
    u=getfa(u),v=getfa(v);
    if(u!=v)
        fa[v]=u;
    

void add(int u,int v,int w)
    e[++tot].v=v;
    e[tot].nxt=head[u];
    e[tot].w=w;
    head[u]=tot;

void add1(int u,int v,int w)
    G[++tot1].v=v;
    G[tot1].w=w;
    G[tot1].nxt=hd[u];
    hd[u]=tot1;
    merge(u,v);

namespace GHT
    int dep[N],cur[N],tmp1[N],tmp2[N];
    bool bfs(int S,int T)
        for(int i=1;i<=n;i++)
            dep[i]=0;
        
        queue<int>q;
        q.push(S);
        dep[S]=1;
        while(!q.empty())
            int u=q.front();
            q.pop();
            for(int i=head[u];i;i=e[i].nxt)
                int v=e[i].v,w=e[i].w;
                if(w&&!dep[v])
                    dep[v]=dep[u]+1;
                    q.push(v);
                    if(v==T)
                        return 1;
                    
                
            
        
        return 0;
    
    int dfs(int u,int flow,int T)
        if(u==T)
            return flow;
        
        int s=0;
        for(int i=cur[u];i;i=e[i].nxt)
            cur[u]=i;
            int v=e[i].v,w=e[i].w;
            if(w&&dep[v]==dep[u]+1)
                int res=dfs(v,min(flow,w),T);
                e[i].w-=res;
                e[i^1].w+=res;
                flow-=res;
                s+=res;
            
            if(!flow)
                break;
            
        
        if(!s)
            dep[u]=0;
        
        return s;
    
    void init()
        for(int i=0;i<=tot;i+=2)
            e[i].w+=e[i^1].w;
            e[i^1].w=0;
        
    
    int dinic(int S,int T)
        init();
        int ans=0;
        while(bfs(S,T))
            memcpy(cur,head,sizeof(head));
            ans+=dfs(S,inf,T);
        
        return ans;
    
    void build(int l,int r)
        if(l>=r)
            return;
        
        int x=id[l],y=id[l+1];
        int flow=dinic(x,y);
        add1(x,y,flow);
        add1(y,x,flow);
        int cnt1=0,cnt2=0;
        for(int i=l;i<=r;i++)
            if(dep[id[i]])
                tmp1[++cnt1]=id[i];
            
            else
                tmp2[++cnt2]=id[i];
            
        
        for(int i=1;i<=cnt1;i++)
            id[l+i-1]=tmp1[i];
        
        for(int i=1;i<=cnt2;i++)
            id[cnt1+i+l-1]=tmp2[i];
        
        build(l,l+cnt1-1);
        build(l+cnt1,r);
    

void clear()
    for(int i=1;i<=n;i++)
        head[i]=hd[i]=0;
        id[i]=i;
        fa[i]=i;
    
    tot=tot1=1;

int ans,x;
void dfs(int u,int father,int mn)
    if(mn<=x)
        ans++;
    
    for(int i=hd[u];i;i=G[i].nxt)
        int v=G[i].v,w=G[i].w;
        if(v==father)
            continue;
        
        dfs(v,u,min(mn,w));
    

void solve()
    read(n),read(m);
    clear();
    for(int i=1,u,v,w;i<=m;i++)
        read(u),read(v),read(w);
        add(u,v,w);
        add(v,u,0);
        add(v,u,w);
        add(u,v,0);
    
    GHT::build(1,n);
    for(int i=2;i<=n;i++)
        if(getfa(i)!=getfa(1))
            add1(i,1,0);
            merge(1,i);
        
    
    int q;
    read(q);
    while(q--)
        read(x);
        ans=0;
        for(int i=1;i<=n;i++)
            dfs(i,0,inf);
        
        write_endl(ans/2);
    


signed main()
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int t;
    read(t);
    while(t--)
        solve();
        puts("");
    
    return 0;

3.[CQOI2016]不同的最小割

又一个板题,建出等价流树,根据性质可得,两点间的最小割为两点在等价流树上的路径上的边权最小值,所以统计有等价流树多少种不同边权即可。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO
    template<typename T>
    inline void read(T &x)
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>\'9\'||ch<\'0\')
            if(ch==\'-\')
                f=-1;
            
            ch=getchar();
        
        while(ch>=\'0\'&&ch<=\'9\')
            x=x*10+(ch-\'0\');
            ch=getchar();
        
        x=(f==1?x:-x);
    
    template<typename T>
    inline void write(T x)
        if(x<0)
            putchar(\'-\');
            x=-x;
        
        if(x>=10)
            write(x/10);
        
        putchar(x%10+\'0\');
    
    template<typename T>
    inline void write_endl(T x)
        write(x);
        putchar(\'\\n\');
    
    template<typename T>
    inline void write_space(T x)
        write(x);
        putchar(\' \');
    

using namespace IO;
const int N=1010,M=600010,Lg=11,inf=1e9;
int n,m,head[N],hd[N],tot1=0,tot=1,id[N];
struct edge
    int v,w,nxt;
e[M];
void add(int u,int v,int w)
    e[++tot].v=v;
    e[tot].nxt=head[u];
    e[tot].w=w;
    head[u]=tot;

set<int>s;
namespace GHT
    int dep[N],cur[N],tmp1[N],tmp2[N];
    bool bfs(int S,int T)
        for(int i=1;i<=n;i++)
            dep[i]=0;
        
        queue<int>q;
        q.push(S);
        dep[S]=1;
        while(!q.empty())
            int u=q.front();
            q.pop();
            for(int i=head[u];i;i=e[i].nxt)
                int v=e[i].v,w=e[i].w;
                if(w&&!dep[v])
                    dep[v]=dep[u]+1;
                    q.push(v);
                    if(v==T)
                        return 1;
                    
                
            
        
        return 0;
    
    int dfs(int u,int flow,int T)
        if(u==T)
            return flow;
        
        int s=0;
        for(int i=cur[u];i;i=e[i].nxt)
            cur[u]=i;
            int v=e[i].v,w=e[i].w;
            if(w&&dep[v]==dep[u]+1)
                int res=dfs(v,min(flow,w),T);
                e[i].w-=res;
                e[i^1].w+=res;
                flow-=res;
                s+=res;
            
            if(!flow)
                break;
            
        
        if(!s)
            dep[u]=0;
        
        return s;
    
    void init()
        for(int i=0;i<=tot;i+=2)
            e[i].w+=e[i^1].w;
            e[i^1].w=0;
        
    
    int dinic(int S,int T)
        init();
        int ans=0;
        while(bfs(S,T))
            memcpy(cur,head,sizeof(head));
            ans+=dfs(S,inf,T);
        
        return ans;
    
    void build(int l,int r)
        if(l>=r)
            return;
        
        int x=id[l],y=id[l+1];
        int flow=dinic(x,y);
        s.insert(flow);
        int cnt1=0,cnt2=0;
        for(int i=l;i<=r;i++)
            if(dep[id[i]])
                tmp1[++cnt1]=id[i];
            
            else
                tmp2[++cnt2]=id[i];
            
        
        for(int i=1;i<=cnt1;i++)
            id[l+i-1]=tmp1[i];
        
        for(int i=1;i<=cnt2;i++)
            id[cnt1+i+l-1]=tmp2[i];
        
        build(l,l+cnt1-1);
        build(l+cnt1,r);
    

signed main()
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    read(n),read(m);
    for(int i=1,u,v,w;i<=m;i++)
        read(u),read(v),read(w);
        add(u,v,w);
        add(v,u,0);
        add(v,u,w);
        add(u,v,0);
    
    for(int i=1;i<=n;i++)
        id[i]=i;
    
    GHT::build(1,n);
    write_endl(s.size());
    return 0;

4.cf343e

先想最暴力的做法,是不是枚举排列,然后跑最小割,求最小割的和的最大值。然后我们发现这样要把任意两个点之间的最小割都求出来,于是建出等价流树。
此时我们再看一下一个排列表示什么,表示的是给每个点标一个dfs序,然后按照dfs序在最小割树上搜一遍,因为等价流树的性质,所以最小割的和等于dfs序相邻的两点的路径上边权最小值的和。所以问题就转化为了求一个dfs序,使得最小割树上边权小的边尽量少走。

先讨论最小边的贡献的次数,只要经过最小边则必然造成一次贡献。因为要遍历整棵树,所以最小边至少经过一次,即最少造成一次贡献。
那么有可能造成更多的贡献吗?这是不可能的。
令最小边的两端点为 \\(u,v\\),若断掉最小边,则一棵树会变成两颗树,分别令 \\(u,v\\) 为根,那么这两棵树分别为树 \\(u\\) 和树 \\(v\\)。一个非常显然的事是,我们可以先遍历完树 \\(u\\),再遍历树 \\(v\\),这样最小边就只会造成 \\(1\\) 次贡献,同理每条边都会造成 \\(1\\) 次贡献,答案的最大值就为等价流树上边权之和,方案可以 \\(n^2\\) 扫一遍。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
using namespace std;
namespace IO
    template<typename T>
    inline void read(T &x)
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>\'9\'||ch<\'0\')
            if(ch==\'-\')
                f=-1;
    
            ch=getchar();
        
        while(ch>=\'0\'&&ch<=\'9\')
            x=x*10+(ch-\'0\');
            ch=getchar();
        
        x=(f==1?x:-x);
    
    template<typename T>
    inline void write(T x)
        if(x<0)
            putchar(\'-\');
            x=-x;
        
        if(x>=10)
            write(x/10);
        
        putchar(x%10+\'0\');
    
    template<typename T>
    inline void write_endl(T x)
        write(x);
        putchar(\'\\n\');
    
    template<typename T>
    inline void write_space(T x)
        write(x);
        putchar(\' \');
    

using namespace IO;
const int N=300,M=1e4,inf=1e9;
int n,m,tot=1,tot1=1,head[N],hd[N],id[N],ans;
struct edge
    int u,v,w,nxt;
e[M],G[M];
void add(int u,int v,int w)
    e[++tot].v=v;
    e[tot].w=w;
    e[tot].nxt=head[u];
    head[u]=tot;

void add1(int u,int v,int w)
    G[++tot1].v=v;
    G[tot1].u=u;
    G[tot1].w=w;
    G[tot1].nxt=hd[u];
    ans+=w;
    hd[u]=tot1;

namespace Gusfield
    int dep[N],cur[N],tmp1[N],tmp2[N];
    bool bfs(int S,int T)
        for(int i=1;i<=n;i++)
            dep[i]=0;
        
        queue<int>q;
        q.push(S);
        dep[S]=1;
        while(!q.empty())
            int u=q.front();
            q.pop();
            for(int i=head[u];i;i=e[i].nxt)
                int v=e[i].v,w=e[i].w;
                if(w&&!dep[v])
                    dep[v]=dep[u]+1;
                    q.push(v);
                    if(v==T)
                        return 1;
                    
                
            
        
        return 0;
    
    int dfs(int u,int flow,int T)
        if(u==T)
            return flow;
        
        int s=0;
        for(int i=cur[u];i;i=e[i].nxt)
            cur[u]=i;
            int v=e[i].v,w=e[i].w;
            if(w&&dep[v]==dep[u]+1)
                int res=dfs(v,min(flow,w),T);
                e[i].w-=res;
                e[i^1].w+=res;
                flow-=res;
                s+=res;
            
            if(!flow)
                break;
            
        
        if(!s)
            dep[u]=0;
        
        return s;
    
    void init()
        for(int i=0;i<=tot;i+=2)
            e[i].w+=e[i^1].w;
            e[i^1].w=0;
        
    
    int dinic(int S,int T)
        init();
        int ans=0;
        while(bfs(S,T))
            memcpy(cur,head,sizeof(head));
            ans+=dfs(S,inf,T);
        
        return ans;
    
    void build(int l,int r)
        if(l>=r)
            return;
        
        int x=id[l],y=id[l+1];
        int flow=dinic(x,y);
        add1(x,y,flow);
        add1(y,x,flow);
        int cnt1=0,cnt2=0;
        for(int i=l;i<=r;i++)
            if(dep[id[i]])
                tmp1[++cnt1]=id[i];
            
            else
                tmp2[++cnt2]=id[i];
            
        
        for(int i=1;i<=cnt1;i++)
            id[l+i-1]=tmp1[i];
        
        for(int i=1;i<=cnt2;i++)
            id[cnt1+i+l-1]=tmp2[i];
        
        build(l,l+cnt1-1);
        build(l+cnt1,r);
    

bool vis[M];
vector<int>s;
void get(int u,int fa)
    s.pb(u);
    for(int i=hd[u];i;i=G[i].nxt)
        int v=G[i].v;
        if(vis[i]||v==fa)
            continue;
        
        get(v,u);
    

void dfs(int u)
    if(s.size()==1)
        write_space(s[0]);
        return;
    
    int id=0,mn=inf;
    for(auto x:s)
        for(int i=hd[x];i;i=G[i].nxt)
            if(vis[i])
                continue;
            
            int w=G[i].w;
            if(w<mn)
                mn=w;
                id=i;
            
        
    
    cerr<<id/2<<endl;
    vis[id]=vis[id^1]=1;
    vector<int>().swap(s);
    get(G[id].u,0);
    dfs(G[id].u);
    vector<int>().swap(s);
    get(G[id].v,0);
    dfs(G[id].v);

void solve()
    read(n),read(m);
    for(int i=1,u,v,w;i<=m;i++)
        read(u),read(v),read(w);
        add(u,v,w);
        add(v,u,0);
        add(v,u,w);
        add(u,v,0);
    
    for(int i=1;i<=n;i++)
        id[i]=i;
    
    Gusfield::build(1,n);
    write_endl(ans>>1);
    for(int i=1;i<=n;i++)
        s.pb(i);
    
    dfs(1);

signed main()
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int t=1;
    while(t--)
        solve();
    
    return 0;

选择监督学习算法或无监督学习算法

【中文标题】选择监督学习算法或无监督学习算法【英文标题】:Choice supervised learning algorithm or unsupervised learning algorithm 【发布时间】:2018-10-01 23:44:38 【问题描述】:

我想检查大量已知为垃圾邮件的电子邮件,以了解是否存在垃圾邮件的子类型。

我应该使用监督学习算法还是无监督学习算法?

谢谢。

【问题讨论】:

Currently best spam filter algorithm的可能重复 【参考方案1】:

监督学习。研究朴素贝叶斯。过去它曾被用来解决这个问题并取得了巨大的成功。

【讨论】:

以上是关于Gusfield算法学习的主要内容,如果未能解决你的问题,请参考以下文章

算法目录

机器学习算法总结——集成学习(AdaboostRandomForest)

选择监督学习算法或无监督学习算法

机器学习算法

机器学习常见算法分类汇总

机器学习算法笔记2. 学习算法与最小均方算法(LMS)