【BZOJ】1001: [BeiJing2006]狼抓兔子 平面图转对偶图&最小割转最大流&最短路

传送门:bzoj1001


题解
先贴一篇博客对偶图的应用
平面图的两个性质:

  • 无重边和自环的无向图
  • 所有边只在端点相交

关于平面图的度和相邻面的介绍上面贴的博客里都有。
平面图都可以转对偶图。
建立方法就是将平面图的每个面(包括外部面)看做一个节点。根据平面图上的每条边,将其划分的两个面所代表的顶点在对偶图中连接一条无向边,若边带权,则边权与原边相同。

接下来说这道题的做法。
首先想到最小割,转最大流。
但是 n2m n 2 m 的复杂度不科学。
考虑平面图转化为对偶图的用处,根据这张从网上扒的图:
这里写图片描述
可知因为连接了外界的点,整个图存在一个经过 s>t s ′ − > t ′ 的环。实际上最小的环就是原图的最小割。因为 s>t s ′ − > t ′ 这条边造成了环,所以删掉这条边,跑一遍 s>t s ′ − > t ′ 的最短路就是原图的最小割。


代码

堆优化dijkstra

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+100;
const int inf=2e9;
int S,T;
int head[N<<1],to[N*6],nxt[N*6],w[N*6],tot;

int n,m;

inline void lk(int u,int v,int cc)
{
   to[++tot]=v;nxt[tot]=head[u];head[u]=tot;w[tot]=cc;
   to[++tot]=u;nxt[tot]=head[v];head[v]=tot;w[tot]=cc;
}

char c;

inline int rd()
{
    c=getchar();int x=0,f=1;
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+(c^48);
    return x*f;
}

struct dij{
    int id[N<<1],tg[N<<1],dis[N<<1],cnt;
    dij(){cnt=0;}

    inline void upp(int x)
    {
        x=tg[x];int k=x>>1;
        for(;dis[id[k]]>dis[id[x]] && k;x=k,k>>=1){
            swap(tg[id[k]],tg[id[x]]);
            swap(id[k],id[x]);
        }
    }

    inline void dnn(int x)
    {
        x=tg[x];int k=x<<1;
        if(k<cnt && dis[id[k]]>dis[id[k|1]]) k|=1;
        for(;k<=cnt && dis[id[k]]<dis[id[x]];){
            swap(tg[id[k]],tg[id[x]]);
            swap(id[x],id[k]);
            x=k;
            k<<=1;if(k<cnt && dis[id[k]]>dis[id[k|1]]) k|=1;
        }
    }

    inline void ins(int x)
    {
        cnt++;id[cnt]=x;tg[x]=cnt;
        if(cnt==1) return;
        upp(x);
    }

    inline int getop(){return id[1];}

}G;

int main(){
    int i,j,bs,x,res;
    scanf("%d%d",&n,&m);
    if(n==m && n==1) {puts("0");return 0;}
    if(n==1 || m==1){
        if(n==1) swap(n,m);//m==1 -> n==1 这里打错才真的调得久
        res=inf;
        for(i=1;i<n;++i) res=min(res,rd());
        printf("%d\n",res);
        return 0;
    }
    S=(n-1)*(m-1)*2+1;T=S+1;

    for(j=1;j<m;++j) lk(S,j,rd());
    for(bs=0,i=2;i<n;++i){
        bs+=(m-1)+(m-1);
        for(j=1;j<m;++j) lk(bs+j-m+1,bs+j,rd());
    }
    for(j=1;j<m;++j) 
      lk(bs+m-1+j,T,rd());

    for(bs=0,i=1;i<n;++i,bs+=(m-1)+(m-1)){
        lk(T,bs+m,rd());
        for(j=1;j<m-1;++j) lk(bs+j,bs+m+j,rd());
        lk(bs+m-1,S,rd());
    }

    for(bs=0,i=1;i<n;++i,bs+=(m-1)+(m-1))
        for(j=1;j<m;++j)
            lk(bs+j,bs+m-1+j,rd());

    for(i=1;i<=T;++i) G.dis[i]=inf,G.ins(i);
    G.dis[S]=0;G.upp(S);
    for(;;){
        x=G.getop();res=G.dis[x];
        if(x==T) break;
        G.dis[x]=inf+1;G.dnn(x);
        for(i=head[x];i;i=nxt[i]){
            j=to[i];if(G.dis[j]<=inf && G.dis[j]>res+w[i]){
                G.dis[j]=res+w[i];
                G.upp(j);
            }
        }
    }
    printf("%d\n",G.dis[T]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值