[CF343E] Pumping Stations

题目大意

n 个点 m 条边的带权无向图。

你需要构造一个排列,收益为\(\Sigma_{i=2}^n mincut(a_{i-1},a_i)\)

\(mincut(S,T)\) 表示图中 S 为源点,T 为汇点的最小割。

求最大的收益,并输出方案

解析

显然,我们需要先把最小割树建出来,然后,问题就转化为了求排列A使得相邻两数对应的点之间的路径上最小的边之和最大。

考虑贪心,最好情况下会是树上的每一条边都被选中了一次,否则只会使更小的边被选择更多次。那么,如何构造满足要求的排列成为了最大问题。边权小的边肯定要后选择,不然就会导致小的边多次被选。所以,先在树上找到最小的边,标记这条边已经被选过。可以发现,这条边把树分成了两棵子树。对于每一棵子树递归处理,在该子树范围内同样找到最小的边。当一个子树只剩下一个点时,输出该点并结束当前递归。这样可以避免重复输出。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define N 202
#define M 1002
using namespace std;
const int inf=1<<30;
struct Edge{
    int u,v,w;
}e[M];
int head[N],ver[M*4],nxt[M*4],cap[M*4],l;
int head1[N],ver1[N*2],nxt1[N*2],edge1[N*2],l1;
int n,m,i,a[N],tmp1[N],tmp2[N],dis[N],S,T,ans,minx=inf,s,t,id,poi[N],num;
bool use[N*2],vis[N];
int read()
{
    char c=getchar();
    int w=0;
    while(c<'0'||c>'9') c=getchar();
    while(c<='9'&&c>='0'){
        w=w*10+c-'0';
        c=getchar();
    }
    return w;
}
void insert(int x,int y,int z)
{
    ver[l]=y;
    cap[l]=z;
    nxt[l]=head[x];
    head[x]=l;
    l++;
    ver[l]=x;
    cap[l]=0;
    nxt[l]=head[y];
    head[y]=l;
    l++;
}
void insert1(int x,int y,int z)
{
    ver1[l1]=y;
    edge1[l1]=z;
    nxt1[l1]=head1[x];
    head1[x]=l1;
    l1++;
}
bool bfs()
{
    queue<int> q;
    memset(dis,-1,sizeof(dis));
    q.push(S);
    dis[S]=0;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nxt[i]){
            int y=ver[i];
            if(dis[y]==-1&&cap[i]>0){
                dis[y]=dis[x]+1;
                q.push(y);
            }
        }
    }
    return (dis[T]>0);
}
int dfs(int x,int flow)
{
    if(x==T||flow==0) return flow;
    int ans=0;
    for(int i=head[x];i!=-1;i=nxt[i]){
        int y=ver[i];
        if(dis[y]==dis[x]+1&&cap[i]>0){
            int a=dfs(y,min(flow,cap[i]));
            flow-=a;
            ans+=a;
            cap[i]-=a;
            cap[i^1]+=a;
        }
        if(flow==0) break;
    }
    if(flow) dis[x]=-inf;
    return ans;
}
int Dinic()
{
    int ans=0;
    memset(head,-1,sizeof(head));
    l=0;
    for(int i=1;i<=m;i++){
        insert(e[i].u,e[i].v,e[i].w);
        insert(e[i].v,e[i].u,e[i].w);
    }
    while(bfs()) ans+=dfs(S,inf);
    return ans;
}
void build(int l,int r)
{
    if(l==r) return;
    S=a[l],T=a[l+1];
    int ans=Dinic(),cnt1=0,cnt2=0;
    insert1(S,T,ans);
    insert1(T,S,ans);
    for(int i=l;i<=r;i++){
        if(dis[a[i]]==-1) tmp2[++cnt2]=a[i];
        else tmp1[++cnt1]=a[i];
    }
    for(int i=l;i<=l+cnt1-1;i++) a[i]=tmp1[i-l+1];
    for(int i=l+cnt1;i<=r;i++) a[i]=tmp2[i-l-cnt1+1];
    build(l,l+cnt1-1);
    build(l+cnt1,r);
}
void cal(int x,int pre)
{
    for(int i=head1[x];i!=-1;i=nxt1[i]){
        int y=ver1[i];
        if(y!=pre){
            ans+=edge1[i];
            if(edge1[i]<minx) minx=edge1[i],s=x,t=y,id=i;
            cal(y,x);
        }
    }
}
int my_comp(const int &x,const int &y)
{
    return edge1[x]>edge1[y];
}
void find(int x,int pre)
{
    for(int i=head1[x];i!=-1;i=nxt1[i]){
        int y=ver1[i];
        if(y!=pre&&!vis[i]){
            if(edge1[i]<minx) minx=edge1[i],s=x,t=y,id=i;
            find(y,x);
        }
    }
}
void print(int x)
{
    minx=inf,s=t=0;
    find(x,0);
    if(s==0){
        poi[++num]=x;
        return;
    }
    int p=s,q=t;
    vis[id]=vis[id^1]=1;
    print(p);print(q);
}
int main()
{
    memset(head1,-1,sizeof(head1));
    n=read();m=read();
    for(i=1;i<=m;i++) e[i].u=read(),e[i].v=read(),e[i].w=read();
    for(i=1;i<=n;i++) a[i]=i;
    build(1,n);
    cal(1,0);
    printf("%d\n",ans);
    int l=s,r=t;
    vis[id]=vis[id^1]=1;
    print(l);print(r);
    for(i=1;i<=n;i++) printf("%d ",poi[i]);
    puts("");
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/LSlzf/p/12194126.html
今日推荐