「Fortuna OJ」Aug 12th – Group A 解题报告

A – 迷宫

这道题挺好的,让我知道了线段树还有这样的操作。

考虑线段树维护一段区间行入口到行出口的最短路,大概的维护方法非常像 Floyd。也没啥好说的,看代码吧。

// A.cpp
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define lson (p << 1)
#define rson ((p << 1) | 1)
#define mid ((l + r) >> 1)

using namespace std;

const int MAX_N = 2e5 + 1, INF = 0x3f3f3f3f;

int n, m, q, org[6][MAX_N];

struct node
{
    int mp[6][6];
} nodes[MAX_N << 2];

void pushup(node &p, node &ls, node &rs)
{
    memset(p.mp, 0x3f, sizeof(p.mp));
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                p.mp[i][j] = min(p.mp[i][j], ls.mp[i][k] + rs.mp[k][j]);
    return;
}

void build(int l, int r, int p)
{
    memset(nodes[p].mp, 0x3f, sizeof(nodes[p].mp));
    if (l == r)
    {
        for (int i = 1; i <= n; i++)
            if (org[i][l])
                for (int j = i; j <= n; j++)
                    if (org[j][l])
                        nodes[p].mp[i][j] = nodes[p].mp[j][i] = j - i + 1;
                    else
                        break;
        return;
    }
    build(l, mid, lson), build(mid + 1, r, rson);
    pushup(nodes[p], nodes[lson], nodes[rson]);
}

void update(int qx, int l, int r, int p)
{
    memset(nodes[p].mp, 0x3f, sizeof(nodes[p].mp));
    if (l == r && l == qx)
    {
        for (int i = 1; i <= n; i++)
            if (org[i][l])
                for (int j = i; j <= n; j++)
                    if (org[j][l])
                        nodes[p].mp[i][j] = nodes[p].mp[j][i] = j - i + 1;
                    else
                        break;
        return;
    }
    if (qx <= mid)
        update(qx, l, mid, lson);
    else
        update(qx, mid + 1, r, rson);
    pushup(nodes[p], nodes[lson], nodes[rson]);
}

node query(int ql, int qr, int l, int r, int p)
{
    if (ql <= l && r <= qr)
        return nodes[p];
    if (qr <= mid)
        return query(ql, qr, l, mid, lson);
    if (mid < ql)
        return query(ql, qr, mid + 1, r, rson);
    node fa, lft = query(ql, qr, l, mid, lson), rgt = query(ql, qr, mid + 1, r, rson);
    pushup(fa, lft, rgt);
    return fa;
}

int main()
{
    scanf("%d%d%d", &n, &m, &q);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d", &org[i][j]);
    build(1, m, 1);
    while (q--)
    {
        int opt, a, b, c, d;
        scanf("%d%d%d", &opt, &a, &b);
        if (opt == 1)
            org[a][b] ^= 1, update(b, 1, m, 1);
        else
        {
            scanf("%d%d", &c, &d);
            if (b > d)
                puts("-1");
            else
            {
                node nd = query(b, d, 1, m, 1);
                if (nd.mp[a][c] == INF)
                    puts("-1");
                else
                    printf("%d\n", nd.mp[a][c] - 1);
            }
        }
    }
    return 0;
}

B – 猛汉王

这道题真毒瘤。

我们分别把有瓶子的和没瓶子的分为黑色点和白色点。整个问题就变成了:在一个图上,把一些无向边的方向进行标记,然后找出一种方案能让单向三角形最多,其中我们只需要求最大值就行了(当然了,最小值的求法是非常相似的)。

我们发现端点异色的边的方向跟 Manhattan 距离和参数 d 相关,所以我们可以尝试预处理一个数组cover[i]代表与点\(i\) Manhattan 距离小于\(d\)的点的个数,其中我们可以扩展一下,设cover[i, j]与\(i, j\)两点 Manhattan 距离均小于\(d\)的点的个数。最后,最大值的答案显然是:

\[ max_ans = \sum_{i, j \in White} max\{ cover[i] – cover[i, j], cover[j] – cover[i. j] \} \]

进行一定的变换:

\[ max_ans = \sum_{i, j\in White} max\{ cover[i], cover[j] \} – cover[i, j] \]

发现\(cover[i, j]\)其实就是对于每一个黑点可选的 Manhattan 距离小于\(d\)的白点个数:

\[ max_ans = \sum_{i, j\in White} max\{ cover[i], cover[j] \} – \sum_{u \in Black} {cover[u] \choose 2} \]

所以我们现在需要计算出\(cover[]\)来进行这个计算。直接求曼哈顿距离小于\(d\)的点数有点困难,我们考虑把一个点的坐标系进行「翻折」,从\((x, y) \to (x + y, x – y)\)。我们会发现,翻折坐标系之后,考虑两点之间的 Manhattan 距离就变成了考虑两点之间的「切比雪夫距离」(\(max\{|x_i – x_j|, |y_i – y_j|\}\),发现变换坐标系之后带入满足绝对值不等式,也就是 Manhattan 距离的定义)。所以,围绕新的坐标,发现\(cover[i]\)等于以点\(i\)为中心半径为\(D\)的正方形内的点数,这就是扫描线经典问题了(POJ 2482),套上就行。

// B.cpp
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define ll long long
#define lson (p << 1)
#define rson ((p << 1) | 1)
#define mid ((l + r) >> 1)

using namespace std;

const int MAX_N = 1e5 + 200;

int n, m, d, tree[MAX_N << 2];
long long min_ans = 0, max_ans = 0, minusComb = 0;

vector<ll> mapping, xlist;

template <class T>
inline T read(T &x)
{
    T data = 0;
    int w = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (isdigit(ch))
        data = 10 * data + ch - '0', ch = getchar();
    return x = data * w;
}

inline int lowbit(int x) { return x & -x; }

void add(int x, int d)
{
    int siz = mapping.size();
    for (; x <= siz; x += lowbit(x))
        tree[x] += d;
}

int query(int x)
{
    int ans = 0;
    for (; x; x -= lowbit(x))
        ans += tree[x];
    return ans;
}

struct point
{
    ll x, y;
    bool operator<(const point &pt) const { return x < pt.x || (x == pt.x && y < pt.y); }
} pts1[MAX_N], pts2[MAX_N];

struct quad
{
    ll x, y, aff;
    bool operator<(const quad &qd) const { return x < qd.x; }
};
vector<quad> vec;

int cover[MAX_N];

int ripe(ll x) { return lower_bound(mapping.begin(), mapping.end(), x) - mapping.begin() + 1; }

void solve()
{
    memset(tree, 0, sizeof(tree));
    vec.clear(), xlist.clear(), mapping.clear();
    for (int i = 1; i <= m; i++)
    {
        vec.push_back(quad{pts2[i].x - d, pts2[i].y, 1});
        vec.push_back(quad{pts2[i].x + d + 1, pts2[i].y, -1});

        xlist.push_back(pts2[i].x - d), xlist.push_back(pts2[i].x + d + 1);

        mapping.push_back(pts2[i].y);
    }

    for (int i = 1; i <= n; i++)
    {
        xlist.push_back(pts1[i].x);
        mapping.push_back(pts1[i].y - d - 1), mapping.push_back(pts1[i].y + d);
    }

    sort(xlist.begin(), xlist.end()), sort(mapping.begin(), mapping.end());
    xlist.erase(unique(xlist.begin(), xlist.end()), xlist.end());
    mapping.erase(unique(mapping.begin(), mapping.end()), mapping.end());

    sort(vec.begin(), vec.end());
    int cur1 = 0, cur2 = 1;
    for (ll x : xlist)
    {
        while (cur1 < vec.size() && vec[cur1].x == x)
            add(ripe(vec[cur1].y), vec[cur1].aff), cur1++;
        while (cur2 <= n && pts1[cur2].x == x)
            cover[cur2] = query(ripe(pts1[cur2].y + d)) - query(ripe(pts1[cur2].y - d - 1)), cur2++;
    }

    sort(cover + 1, cover + 1 + n);

    for (int i = 1; i <= n; i++)
    {
        min_ans += 1LL * (n - i) * cover[i];
        max_ans += 1LL * (i - 1) * cover[i];
        minusComb += 1LL * (1LL * cover[i] * (cover[i] - 1) >> 1);
    }
}

int main()
{
    /*
    freopen("mhw.in", "r", stdin);
    freopen("mhw.out", "w", stdout);
    */
    read(n), read(m), read(d);
    for (int i = 1, x, y; i <= n; i++)
    {
        read(x), read(y);
        pts1[i].x = x + y, pts1[i].y = x - y;
    }
    for (int i = 1, x, y; i <= m; i++)
    {
        read(x), read(y);
        pts2[i].x = x + y, pts2[i].y = x - y;
    }
    sort(pts1 + 1, pts1 + 1 + n);
    sort(pts2 + 1, pts2 + 1 + m);

    solve();
    swap(n, m), swap(pts1, pts2);

    solve();

    printf("%lld %lld", min_ans - minusComb, max_ans - minusComb);
    return 0;
}

 

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *