BZOJ 4200 - [Noi2015]小园丁与老司机

Published on 2016-05-15

题目地址

描述

UOJ 传送门:【NOI2015】小园丁与老司机

分析

这题超级麻烦,如果不太会这种 DAG 上的 DP + 同层转移,也不会上下界的网络流的话,恐怕真的是灾难。
对于不会 上下界的网络流 的同学,建议先直接用我代码里面的模版,AC 之后再自己写一遍。

用法:调用:先 addEdge(from, to, bound, cap),然后调用 maxFlow(s, t) 会返回最小流,如果没有可行流,返回 -1。

直接说这个题的正解了。
加入点 (0,0)(0, 0) 表示原点,下标为 0。
对于输出的 1, 2 行,我们使用 DAG 上的 DP + 同层转移来解决。因为没有 y<0y < 0 的点,所以我们按 yy 坐标分层,yy 坐标相同的在一层,不同层 yy 坐标依次递增。不同层之间连向上边和两个斜边,同层不连边。
这个怎么做到呢?我们先把所有点按 xx 为第一关键字,yy 为第二关键字排序。如果排好序后 xx 坐标为 x1x_1 的点的下标范围是 [l,r][l, r] 的话,我们连边 (i,i+1),i[l,r1](i, i + 1), i \in [l, r - 1] 表示向上边。由于必须到达该方向上距离最近的尚未许愿的树且没有向下边,所以只能这样连。
接着看斜边,观察到所有为右上 4545^{\circ} 关系的点,必定满足 xiyi=kx_i - y_i = k,k 是一个定值。所以把所有点按 xyx - y 为第一关键字,yy 为第二关键字排序,与刚刚一样做就行了。而所有为左上 4545^{\circ} 关系的点,必定满足 xi+yi=kx_i + y_i = k,k 是一个定值,所以把所有点按 x+yx + y 为第一关键字,yy 为第二关键字排序,与刚刚一样做就行了。

连边完毕。考虑 DP,设 did_i 为从原点出发到 ii 最多能经过多少个点(不含原点),初始值都为 -\inftyd0=0d_0 = 0,则 maxdi,iV\max d_i, i\in V 就是答案。
DP 的过程。按 yy 分层,yy 从小到大一层的做。做到某一层的时候,这一层不考虑同层转移的值已经求出来了,那么这一层进行同层转移(可从左边,右边向某个点转移,预处理一下即可 O(n)O(n)),转移完毕后利用连接的边更新高一层的答案。

打印解。这个可以从某个最优点开始,一直找是谁能转移到它,与之前的 DP 类似,注意有 2 种转移。

标记所有在最优策略上的边。这个极为麻烦,要倒着做,一开始标记所有最优点,接着所有能转移到最优点的边要标记。转移的时候有同层和不同层的区别,要记录某个点是否经过两次转移还是一次转移爬上更高一层。

细节很多很多,不可能说全,我代码有比较详细的注释,大家可以看看。
这篇写的很敷衍,真的是细节太多了啊,说不完。

代码

//  Created by Sengxian on 5/14/16.
//  Copyright (c) 2016年 Sengxian. All rights reserved.
//  BZOJ 4200 NOI 2015 D2T3 有上下界网络流 + dp
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
inline int ReadInt() {
    static int n, ch;
    static bool flag;
    n = 0, ch = getchar(), flag = false;
    while (!isdigit(ch)) flag |= ch == '-', ch = getchar();
    while (isdigit(ch)) n = (n << 3) + (n << 1) + ch - '0', ch = getchar();
    return flag ? -n : n;
}

const int maxn = 50000 + 3, INF = 0x3f3f3f3f;

struct edge {
    int to, cap, rev;
    bool real;
    edge(int to = 0, int cap = 0, int rev = 0): to(to), cap(cap), rev(rev) {}
};
struct DinicWithBound {
    static const int maxNode = maxn + 10;
    vector<edge> G[maxNode];
    int level[maxNode], iter[maxNode];
    inline void addEdge(int f, int t, int c) {
        G[f].push_back(edge(t, c, G[t].size()));
        G[t].push_back(edge(f, 0, G[f].size() - 1));
    }
    bool Bfs(int s, int t) {
        memset(level, -1, sizeof level);
        queue<int> q;
        level[s] = 0;
        q.push(s);
        while (!q.empty()) {
            int cur = q.front(); q.pop();
            for (int i = 0; i < (int)G[cur].size(); ++i) {
                edge &e = G[cur][i];
                if (e.cap && level[e.to] == -1) {
                    level[e.to] = level[cur] + 1;
                    q.push(e.to);
                }
            }
        }
        return ~level[t];
    }
    int Dfs(int cur, int flow, int t) {
        if (cur == t || flow == 0) return flow;
        for (int &i = iter[cur]; i < (int)G[cur].size(); ++i) {
            edge &e = G[cur][i];
            if (e.cap && level[e.to] == level[cur] + 1) {
                int d = Dfs(e.to, min(flow, e.cap), t);
                if (d) {
                    e.cap -= d;
                    G[e.to][e.rev].cap += d;
                    return d;
                }
            }
        }
        return 0;
    }
    int maxFlow(int s, int t) {
        int d, flow = 0;
        while (Bfs(s, t)) {
            memset(iter, 0, sizeof iter);
            while (d = Dfs(s, INF, t), d) flow += d;
        }
        return flow;
    }
    int in[maxNode];
    inline void addEdge(int f, int t, int b, int c) {
        in[f] -= b; //必须流
        in[t] += b; //必须流
        addEdge(f, t, c - b); //自由流
    }
    int minFlow(int s, int t) {
        int S = t + 1, T = S + 1, allFlow = 0;
        for (int i = 0; i <= t; ++i)
            if (in[i] > 0) addEdge(S, i, in[i]), allFlow += in[i];
            else if (in[i] < 0) addEdge(i, T, abs(in[i]));
        int idx = G[s].size();
        addEdge(t, s, INF);
        if (maxFlow(S, T) != allFlow) return -1;
        int flow = 0;
        {    
            edge &e = G[s][idx];
            flow = e.cap;
            e.cap = G[e.to][e.rev].cap = 0;
        }
        for (int i = 0; i < G[S].size(); ++i) {
            edge &e = G[S][i];
            e.cap = G[e.to][e.rev].cap = 0;
        }
        for (int i = 0; i < G[T].size(); ++i) {
            edge &e = G[T][i];
            e.cap = G[e.to][e.rev].cap = 0;
        }
        return flow - maxFlow(t, s);
    }
}Dinic;

struct point {
    int x, y, id;
    point(int x = 0, int y = 0, int id = 0): x(x), y(y), id(id) {}
    bool operator < (const point &p) const {
        return y < p.y || (y == p.y && x < p.x);
    }
    inline void get() {x = ReadInt(), y = ReadInt();}
}ps[maxn], ori_ps[maxn];
vector<int> G[maxn], rG[maxn];
int n;

inline bool cmp1(const point &p1, const point &p2) {return p1.x < p2.x || (p1.x == p2.x && p1.y < p2.y);}
inline bool cmp2(const point &p1, const point &p2) {return (p1.x + p1.y) < (p2.x + p2.y) || ((p1.x + p1.y) == (p2.x + p2.y) && p1.y < p2.y);}
inline bool cmp3(const point &p1, const point &p2) {return (p1.x - p1.y) < (p2.x - p2.y) || ((p1.x - p1.y) == (p2.x - p2.y) && p1.y < p2.y);}


void process() {
    sort(ps, ps + n, cmp1);
    for (int i = 0; i < n - 1; ++i)
        if (ps[i].x == ps[i + 1].x) G[ps[i].id].push_back(ps[i + 1].id), rG[ps[i + 1].id].push_back(ps[i].id);
    sort(ps, ps + n, cmp2);
    for (int i = 0; i < n - 1; ++i)
        if ((ps[i].x + ps[i].y) == (ps[i + 1].x + ps[i + 1].y)) G[ps[i].id].push_back(ps[i + 1].id), rG[ps[i + 1].id].push_back(ps[i].id);
    sort(ps, ps + n, cmp3);
    for (int i = 0; i < n - 1; ++i)
        if ((ps[i].x - ps[i].y) == (ps[i + 1].x - ps[i + 1].y)) G[ps[i].id].push_back(ps[i + 1].id), rG[ps[i + 1].id].push_back(ps[i].id);
    // 不加横向边是因为会破坏DAG,所以要分层处理
}

int d[maxn], f[maxn], l[maxn], r[maxn], id[maxn], pre[maxn];
map<int, int> lb, rb;

void dp() {
    sort(ps, ps + n);
    fill(d, d + n, -INF);
    d[ps[0].id] = 0; //d[i] 到 i 最多可以经过多少个点
    for (int i = 0; i < n; ++i) id[ps[i].id] = i;
    for (int i = 0, j = 0; i < n; i = j) {
        for (j = i; ps[j].y == ps[j + 1].y; ++j); //找到这一层的末尾
        ++j;
        lb[ps[i].y] = i, rb[ps[i].y] = j;
        r[j] = -INF;
        for (int k = i; k < j; ++k) f[ps[k].id] = d[ps[k].id]; //不考虑同层转移的值
        for (int k = i; k < j; ++k) l[k] = max(k == i ? -INF : l[k - 1], d[ps[k].id]); // 更新左边的最值
        for (int k = j - 1; k >= i; --k) r[k] = max(r[k + 1], d[ps[k].id]); // 更新右边的最值
        for (int k = i; k < j; ++k) {
            //可以到从之前某个点上到这一层,再走到边界,再走到 k,这就是同层转移的过程
            int lv = (k == i ? -INF : l[k - 1]) + (k - i);
            int rv =  r[k + 1] + (j - k - 1);
            d[ps[k].id] = max(d[ps[k].id], max(lv, rv));
        }
        for (int k = i; k < j; ++k) {
            int u = ps[k].id;
            for (int x = 0; x < (int)G[u].size(); ++x) {
                int v = G[u][x];
                if (d[v] < d[u] + 1) pre[v] = u;
                d[v] = max(d[v], d[u] + 1);
            }
        }
    }
}

int Max = 0;
void printSolution() {
    int cur = 0;
    for (int i = 0; i < n; ++i)
        if (d[i] > Max) Max = d[i], cur = i;
    printf("%d\n", Max);
    vector<int> vec;
    int *use = d;
    while (cur) {
        vec.push_back(cur);
        bool flag = false;
        //非同层转移(即用G转移)
        for (int i = 0; i < rG[cur].size(); ++i) {
            int v = rG[cur][i];
            if (d[v] + 1 == use[cur]) { 
                use = d; //又可以同层转移了
                cur = v, flag = true; break;
            }
        }
        if (flag) continue;
        use = f; //到这里,下一波不能同层转移。
        //同层转移
        int l = lb[ori_ps[cur].y], r = rb[ori_ps[cur].y];
        for (int i = l; i < id[cur]; ++i)
            if (d[cur] == f[ps[i].id] + (id[cur] - l)) {
                for (int k = id[cur] - 1; k > i; --k) vec.push_back(ps[k].id);
                for (int k = l; k < i; ++k) vec.push_back(ps[k].id);
                cur = ps[i].id; flag = true; break;
            }
        if (flag) continue;        
        for (int i = id[cur] + 1; i < r; ++i)
            if (d[cur] == f[ps[i].id] + (r - id[cur] - 1)) {
                for (int k = id[cur] + 1; k < i; ++k) vec.push_back(ps[k].id);
                for (int k = r - 1; k > i; --k) vec.push_back(ps[k].id);
                cur = ps[i].id; flag = true; break;
            }
        assert(flag);
    }
    for (int i = vec.size() - 1; ~i; --i)
        printf("%d%c", vec[i], i ? ' ' : '\n');
}

bool mark[maxn];
int type[maxn];
void solve() {
    set<int> dict;
    int s = n, t = n + 1;
    for (int i = 0; i < n; ++i) Dinic.addEdge(s, i, 0, INF), Dinic.addEdge(i, t, 0, INF);
    for (int i = n - 1, j; ~i; i = j - 1) {
        for (j = i; j && ps[j].y == ps[j - 1].y; --j); //找到这一层的起始位置
        //开始考虑非同层转移
        //type & 1 的,表示这个点由一次同层转移一次非同层转移爬上去,故判 f
        //type & 2 的,表示这个点由一次非同层转移同层爬上去,故判 d
        for (int k = j; k <= i; ++k) {
            int u = ps[k].id;
            if (d[u] == Max) mark[u] = true, type[u] |= 2; //2 表示可转移到 d
            for (int t = 0; t < (int)G[u].size(); ++t) {
                int v = G[u][t];
                if (mark[v] && ((type[v] & 2) && d[u] + 1 == d[v]) || ((type[v] & 1) && d[u] + 1 == f[v])) {
                    Dinic.addEdge(u, v, 1, INF), mark[u] = true;
                    type[u] |= 2;
                }
            }
        }
        //考虑同层转移
        for (int k = i; k >= j; --k) {
            if (dict.count(f[ps[k].id])) mark[ps[k].id] = true, type[ps[k].id] |= 1;
            if (mark[ps[k].id] && (type[ps[k].id] & 2)) dict.insert(d[ps[k].id] - (k - j)); //因为只有非同层转移爬上去的点才能插入被同层转移
        }
        dict.clear();
        for (int k = j; k <= i; ++k) {
            if (dict.count(f[ps[k].id])) mark[ps[k].id] = true, type[ps[k].id] |= 1;
            if (mark[ps[k].id] && (type[ps[k].id] & 2)) dict.insert(d[ps[k].id] - (i - k));
        }
        dict.clear();
    }
    printf("%d\n", Dinic.minFlow(s, t));
}

int main() {
    n = ReadInt() + 1;
    ori_ps[0] = ps[0] = point(0, 0, 0);
    for (int i = 1; i < n; ++i) ps[i].get(), ps[i].id = i, ori_ps[i] = ps[i];
    process();
    dp();
    printSolution();
    solve();
    return 0;
}