【SNOI2019】通信【分治优化建图+费用流】

题目链接


首先,如果O(N^2)的方式建边的话,那么边复杂度会很高,那么就会TLE了,得分一般在80分。

O(N^2)建图的图示:

于是,就很容易发现,真正O(N^2)的边复杂度来源于求1~N号点的是由非0结点得来的贡献也就是一个二分图的匹配问题了。这里要的也是我们对于这O(N^2)的边复杂度的优化。这里,我们可以将边复杂度优化到O(N*log(N))。

于是利用到了分治的思想,求每个点对之后点的贡献,我们可以利用分治来进行求解。

现在,对于这N个点, 我们首先肯定是要求1~N的相互之间的贡献,不妨将问题变成,想要求[L, R]这个闭区间内的左区间这块的点对右区间这块的点。当我们递推的方式向下的时候就可以求的所有的点对间的贡献了。

我们可以利用这样的方式来建边,这样点复杂度会变成O(N*log(N)),但是边复杂度也会下降到O(N*log(N))。

具体的优化方式呢,就是,我们现在对区间[L, R]找到中间的点mid = (L + R) / 2;然后,再是对[L, R]内的所有的点权都提出来,升序去重,于是得到了一些点值,现在我们就开这么多的新点,并且值相近的两点相互连边,流为INF因为可以无穷的通过,但是费用是他们两的权值差,因为要满足抵达的消耗是规范的,然后在[L, mid]区间内的元素去和自己对应值的点链接过去,在[mid + 1, R]区间内的点,被自己对应值的点链接过去。于是,我们就可以表示出来他们两者的贡献了。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <bitset>
#include <unordered_map>
#include <unordered_set>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f3f3f3f3f
#define HalF (l + r)>>1
#define lsn rt<<1
#define rsn rt<<1|1
#define Lson lsn, l, mid
#define Rson rsn, mid+1, r
#define QL Lson, ql, qr
#define QR Rson, ql, qr
#define myself rt, l, r
using namespace std;
typedef unsigned long long ull;
typedef unsigned int uit;
typedef long long ll;
const int maxN = 2e4 + 10, maxM = 2e6 + 7;
int N, head[maxN], cnt;
ll du[maxN], fl[maxM], W, a[maxN];
struct Eddge
{
    int nex, u, v; ll flow, cost;
    Eddge(int a=-1, int b=0, int c=0, ll d=0, ll f=0):nex(a), u(b), v(c), flow(d), cost(f) {}
}edge[maxM];
inline void addEddge(int u, int v, ll f, ll w)
{
    edge[cnt] = Eddge(head[u], u, v, f, w);
    head[u] = cnt++;
}
inline void _add(int u, int v, ll f, ll w) { addEddge(u, v, f, w); addEddge(v, u, 0, -w); }
struct MaxFlow_MinCost
{
    int pre[maxN], S, T, node; ll Flow[maxN], dist[maxN];
    queue<int> Q;
    bool inque[maxN];
    inline bool spfa()
    {
        for(int i=0; i<=node; i++) { pre[i] = -1; dist[i] = INF; inque[i] = false; }
        while(!Q.empty()) Q.pop();
        Q.push(S); dist[S] = 0; inque[S] = true; Flow[S] = INF;
        while(!Q.empty())
        {
            int u = Q.front(); Q.pop(); inque[u] = false;
            ll f, w;
            for(int i=head[u], v; ~i; i=edge[i].nex)
            {
                v = edge[i].v; f = edge[i].flow; w = edge[i].cost;
                if(f && dist[v] > dist[u] + w)
                {
                    dist[v] = dist[u] + w;
                    Flow[v] = min(Flow[u], f);
                    pre[v] = i;
                    if(!inque[v])
                    {
                        inque[v] = true;
                        Q.push(v);
                    }
                }
            }
        }
        return ~pre[T];
    }
    inline ll EK()
    {
        ll sum_Cost = 0;
        while(spfa())
        {
            int now = T, las = pre[now];
            while(now ^ S)
            {
                edge[las].flow -= Flow[T];
                edge[las ^ 1].flow += Flow[T];
                now = edge[las].u;
                las = pre[now];
            }
            sum_Cost += dist[T] * Flow[T];
        }
        return sum_Cost;
    }
} MF;
ll cop[maxN];
int tot = 0;
void cdq(int l, int r)
{
    if(l == r) return;
    int mid = HalF, _UP = r - l + 1;
    cdq(l, mid); cdq(mid + 1, r);
    for(int i=1; i<=_UP; i++) cop[i] = a[l + i - 1];
    sort(cop + 1, cop + _UP + 1);
    _UP = (int)(unique(cop + 1, cop + _UP + 1) - cop - 1);
    for(int i=1; i<=_UP; i++) head[tot + i] = -1;
    for(int i=1; i<_UP; i++)
    {
        _add(tot + i, tot + i + 1, INF, cop[i + 1] - cop[i]);
        _add(tot + i + 1, tot + i, INF, cop[i + 1] - cop[i]);
    }
    for(int i=l, id; i<=mid; i++)
    {
        id = (int)(lower_bound(cop + 1, cop + _UP + 1, a[i]) - cop);
        _add(i, tot + id, 1, 0);
    }
    for(int i=mid + 1, id; i<=r; i++)
    {
        id = (int)(lower_bound(cop + 1, cop + _UP + 1, a[i]) - cop);
        _add(tot + id, i + N, 1, 0);
    }
    tot += _UP;
}
inline void init()
{
    cnt = 0; MF.S = 2 * N + 1; MF.T = 2 * N + 2; tot = MF.T;
    for(int i=0; i<=2 * N + 2; i++) head[i] = -1;
}
int main()
{
    scanf("%d%lld", &N, &W);
    init();
    for(int i=1; i<=N; i++) scanf("%lld", &a[i]);
    _add(MF.S, 0, N, 0);
    for(int i=1; i<=N; i++) _add(MF.S, i, 1, 0);
    for(int i=1; i<=N; i++) { _add(0, i + N, 1, W); _add(i + N, MF.T, 1, 0); }
    cdq(1, N); MF.node = tot;
    printf("%lld\n", MF.EK());
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wuliwuliii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值