题解 洛谷 P5331 【[SNOI2019]通信】

考虑用费用流解决本题。

每个哨站看作一个点,并将其拆为两个点,建图方式为:

\(S \longrightarrow x_i\) 容量为\(1\),费用为\(0\)

\(x_i \longrightarrow T\) 容量为\(1\),费用为\(w\)

\(x_i \longrightarrow x^\prime_j\ (i>j)\) 容量为\(1\),费用为\(|a_i-a_j|\)

\(x^\prime_i \longrightarrow T\) 容量为\(1\),费用为\(0\)

这样就可以满足题目要求了,跑最小费用最大流即可求解,但发现边数为\(n^2\)级别,所以要考虑优化建图。

一个点在和拆出来的点连边时,只会向下标比其小的点连边,所以可以通过分治来实现优化建图。

对于区间\([l,r]\),分成两个区间\([l,mid]\)\([mid+1,r]\),每次从右区间向左区间连边。还需考虑费用的绝对值如何处理,可以把这一区间内所有的权值排序去重,然后连成一条链,链上边的费用为相邻两点的权值之差,然后把这条链作为连接右区间和左区间的虚点。

区间内的点向虚点连边时,只需找到其权值所对应到链上的位置,然后向对应位置连边即可,这样就通过累积权值之间的差值实现了费用的绝对值。

\(code:\)

#include<bits/stdc++.h>
#define maxn 800010
#define inf 2000000000
using namespace std;
typedef long long ll;
template<typename T> inline void read(T &x)
{
    x=0;char c=getchar();bool flag=false;
    while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    if(flag)x=-x;
}
int n,w,tot,cnt,s,t;
int p1[maxn],p2[maxn];
ll a[maxn],b[maxn],dis[maxn];
bool vis[maxn];
struct edge
{
    int to,nxt,v;
    ll c;
}e[maxn];
int head[maxn],edge_cnt=1;
void add(int from,int to,int val,ll cost)
{
    e[++edge_cnt]=(edge){to,head[from],val,cost};
	head[from]=edge_cnt;
    e[++edge_cnt]=(edge){from,head[to],0,-cost};
	head[to]=edge_cnt;
}
bool spfa()
{
    for(int i=1;i<=tot;++i) vis[i]=0,dis[i]=inf;
    queue<int> q;
    q.push(s),dis[s]=0,vis[s]=true;
    while(!q.empty())
    {
        int x=q.front();
        q.pop(),vis[x]=false;
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to,v=e[i].v;
            ll c=e[i].c;
            if(v&&dis[y]>dis[x]+c)
            {
                dis[y]=dis[x]+c;
                if(!vis[y]) q.push(y),vis[y]=true;
            }
        }
    }
    return dis[t]!=inf;
}
int dfs(int x,int lim)
{
    if(x==t) return lim;
    vis[x]=true;
    int res=lim,flow;
    for(int i=head[x];i;i=e[i].nxt)
    {
        int y=e[i].to,v=e[i].v;
        ll c=e[i].c;
        if(!v||dis[y]!=dis[x]+c||vis[y]) continue;
        if(flow=dfs(y,min(res,v)))
        {
            res-=flow;
            e[i].v-=flow;
            e[i^1].v+=flow;
            if(!res) break;
        }
    }
    return lim-res;
}
ll dinic()
{
    int flow;
    ll sum=0;
    while(spfa())
        while(flow=dfs(s,inf))
            sum+=flow*dis[t];
    return sum;
}
void solve(int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    solve(l,mid),solve(mid+1,r),cnt=0;
    for(int i=l;i<=r;++i) b[++cnt]=a[i];
    sort(b+1,b+cnt+1),cnt=unique(b+1,b+cnt+1)-b-1;
    for(int i=1;i<cnt;++i)
    {
        add(tot+i,tot+i+1,inf,b[i+1]-b[i]);
        add(tot+i+1,tot+i,inf,b[i+1]-b[i]);
    }
    for(int i=l;i<=r;++i)
    {
        int pos=lower_bound(b+1,b+cnt+1,a[i])-b;
        if(i>mid) add(p1[i],tot+pos,1,0);
        else add(tot+pos,p2[i],1,0);
    }
    tot+=cnt;
}
int main()
{
    read(n),read(w),s=++tot,t=++tot;
    for(int i=1;i<=n;++i) read(a[i]);
    for(int i=1;i<=n;++i)
    {
        p1[i]=++tot,p2[i]=++tot;
        add(s,p1[i],1,0),add(p1[i],t,1,w),add(p2[i],t,1,0);
    }
    solve(1,n),printf("%lld",dinic());
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/lhm-/p/12969783.html