• 题解 洛谷 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;
    }
    
  • 相关阅读:
    hdu 3032 Nim or not Nim? (SG函数博弈+打表找规律)
    HDU 2147 kiki's game(博弈)
    C++学习47 文件的概念 文件流类与文件流对象 文件的打开与关闭
    C++学习46 getline()函数读入一行字符 一些与输入有关的istream类成员函数
    C++学习45 流成员函数put输出单个字符 cin输入流详解 get()函数读入一个字符
    C++学习44 格式化输出,C++输出格式控制
    C++学习43 输入输出有关的类和对象
    C++学习42 输入和输出的概念
    C++学习41 exception类
    C++学习40 抛出自己的异常
  • 原文地址:https://www.cnblogs.com/lhm-/p/12969783.html
Copyright © 2020-2023  润新知