「2019纪中集训Day12」解题报告

T1、迷宫

一个 $ n \times m \ (n \leq 5, m \leq 10 ^ 5) $ 的矩阵,\(0\) 表示格子不能走,\(1\) 表示格子可以走,只能向上、下、右三个方向走。有 $ q \ (q \leq 5 \times 10 ^ 4) $ 次操作,操作有两种:
1、修改某个格子的类型;
2、查询从 $ (a, b) $ 到 \((c, d)\) 的最短路,不可达输出 \(-1\)。 (保证后者在前者右边)。

\(Sol\)

观察到 \(n \leq 5\),这是很重要的提示。
在左右方向上建线段树,线段树每个叶子节点为一个矩阵,该矩阵描述了这一列上点的连通关系,特别地,若该列某一行的格子不能走,它到自己在矩阵中应表示为不可到达;
合并时枚举中间点 \(n ^ 3\) 暴力合并。
然而我考场上写了常数较大的做法:每个叶子节点的矩阵描述这一列到下一列的连通关系,合并同理;这样写修改时要改两个点,然后就没了。

\(Source\)

#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}

const int N = 2e5 + 5;

int n, m, q, mp[7][N];

template<typename T>inline void chk_min(T &_, T __) {
    _ = !~_ ? __ : (_ < __ ? _ : __);
}
struct node {
    int a[6][6];
    node () {
        memset(a, -1, sizeof(a));
    }
    inline int* operator [] (const int x) {
        return a[x];
    }
    inline node operator + (node b) const {
        node ret;
        for (int k = 1; k <= n; ++k)
            for (int i = 1; i <= n; ++i)
                if (~a[i][k])
                    for (int j = 1; j <= n; ++j)
                        if (~b[k][j])
                            chk_min(ret[i][j], a[i][k] + b[k][j] + 1);
        return ret;
    }
} ;
struct segment_tree {
    node t[N << 2];
    void init(int x, int p) {
        for (int i = 1, j; i <= n; ++i) {
            for (j = i; j <= n && mp[j][x]; ++j)
                t[p][i][j] = j - i;
            for (; j <= n; ++j)
                t[p][i][j] = -1;
            for (j = i; j && mp[j][x]; --j)
                t[p][i][j] = i - j;
            for (; j; --j)
                t[p][i][j] = -1;
        }
    }
    inline void push_up(int p) {
        t[p] = t[p << 1] + t[p << 1 | 1];
    }
    void build(int tl, int tr, int p) {
        if (tl == tr)
            return init(tl, p);
        int mid = (tl + tr) >> 1;
        build(tl, mid, p << 1), build(mid + 1, tr, p << 1 | 1);
        push_up(p);
    }
    void modify(int pos, int tl, int tr, int p) {
        if (tl == tr)
            return init(tl, p);
        int mid = (tl + tr) >> 1;
        if (mid >= pos)
            modify(pos, tl, mid, p << 1);
        else
            modify(pos, mid + 1, tr, p << 1 | 1);
        push_up(p);
    }
    node query(int l, int r, int tl, int tr, int p) {
        if (l <= tl && tr <= r)
            return t[p];
        int mid = (tl + tr) >> 1;
        if (mid < l)
            return query(l, r, mid + 1, tr, p << 1 | 1);
        if (mid >= r)
            return query(l, r, tl, mid, p << 1);
        return query(l, r, tl, mid, p << 1) +
               query(l, r, mid + 1, tr, p << 1 | 1);
    }
} T;

int main() {
    //freopen("in", "r", stdin);
    freopen("maze.in", "r", stdin);
    freopen("maze.out", "w", stdout);
    n = in(), m = in(), q = in();
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            mp[i][j] = in();
    T.build(1, m, 1);

    int a, b, c, d;
    node res;
    while (q--) {
        if (in() == 1) {
            a = in(), b = in();
            mp[a][b] ^= 1;
            T.modify(b, 1, m, 1);
        } else {
            a = in(), b = in(), c = in(), d = in();
            res = T.query(b, d, 1, m, 1);
            printf("%d\n", res[a][c]);
        }
    }
    return 0;
}

T2、猛汉王

平面上有 \(n \ (n \leq 10 ^ 5)\) 个黑点,\(m \ (n \leq 10 ^ 5)\) 个白点,若黑点 \(x\) 与白点 \(y\) 的曼哈顿距离小于等于 \(d \ (d \leq 10 ^ 9)\),则 \(x\)\(y\) 连边,否则 \(y\)\(x\) 连边。
同色点之间的连边方向不确定,求至少有一个黑点和白点的三元环的最小值和最大值。

\(Sol\)

显然地,三元环上有两个同色点。
由于我们不关心异色边是怎么连的,只关心是否能为同向环上的边,所以黑色点和白色点同理。
\(cover(x)\) 表示 \(x\) 能覆盖的白色点,\(cover ( x, y )\) 表示 \(x, y\) 共同覆盖到的白色点。
以下以最大值为例 (最小值同理)。

\[\begin{eqnarray} Ans &=& \sum_{x, y \in S} \max \{cover(x) - cover{(x,y)}, cover(y) - cover(x, y) \} \notag \\ &=& \sum_{x, y \in S} \max \{ cover(x), cover(y) \} - \sum_{x, y \in S} cover(x, y) \notag \\ &=& \sum_{x, y \in S} \max \{ cover(x), cover(y) \} - \sum_{x \in S} \binom{cover(x)}{2} \notag \\ \end{eqnarray} \]

只要求出 \(cover(x)\) 即可;
扫描线维护以下就行了。

\(Source\)

#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 1e5 + 5;
struct node {
    int x, y;
} a[N], b[N];
struct options {
    long long x; int y, z, id;
    int typ;
} opt[N * 3];

int n, m, d, nn, cntx[N], cnty[N];
long long ans_min, ans_max;

long long tmp[N * 3];
inline bool cmpx(const options &i, const options &j) {
    if (i.x == j.x)
        return i.typ == 0 ? 1 : j.typ != 0;
    return i.x < j.x;
}
void prep() {
    nn = 0;
    for (int i = 1; i <= m; ++i)
        tmp[i] = b[i].y;
    for (int i = 1; i <= n; ++i)
        tmp[i + m] = 1ll * a[i].y - d;
    for (int i = 1; i <= n; ++i)
        tmp[i + n + m] = 1ll * a[i].y + d;
    std::sort(tmp + 1, tmp + 1 + n + n + m);
    nn = std::unique(tmp + 1, tmp + 1 + n + n + m) - tmp - 1;
    for (int i = 1; i <= m; ++i) {
        opt[i].typ = opt[i].id = 0;
        opt[i].x = b[i].x;
        opt[i].y = std::lower_bound(tmp + 1, tmp + 1 + nn, b[i].y) - tmp;
        opt[i].z = 0;
    }
    for (int i = 1; i <= n; ++i) {
        opt[i + m].typ = -1;
        opt[i + m].id = i;
        opt[i + m].x = 1ll * a[i].x - d - 1;

        opt[i + n + m].typ = 1;
        opt[i + n + m].id = i;
        opt[i + n + m].x = 1ll * a[i].x + d;
        
        opt[i + m].y = opt[i + n + m].y = std::lower_bound(tmp + 1, tmp + 1 + nn, 1ll * a[i].y - d) - tmp;
        opt[i + m].z = opt[i + n + m].z = std::lower_bound(tmp + 1, tmp + 1 + nn, 1ll * a[i].y + d) - tmp;
    }
    std::sort(opt + 1, opt + 1 + n + n + m, cmpx);
}

struct binary_index_tree {
    int a[N * 3];
    inline void init() { memset(a, 0, sizeof(a)); }
    void insert(int p, int k) { for (; p <= nn; p += (p & -p)) a[p] += k; }
    int ask(int p, int ret = 0) { for (; p; p -= (p & -p)) ret += a[p]; return ret; }
} bit;

void work() {
    prep();
    bit.init();
    for (int i = 1; i <= n + n + m; ++i) {
        if (!opt[i].typ) {
            bit.insert(opt[i].y, 1);
        } else {
            cntx[opt[i].id] += opt[i].typ * (bit.ask(opt[i].z) - bit.ask(opt[i].y - 1));
        }
    }
}

int main() {
    //freopen("in", "r", stdin);
    freopen("mhw.in", "r", stdin);
    freopen("mhw.out", "w", stdout);
    n = in(), m = in(), d = in();
    for (int i = 1; i <= n; ++i) {
        a[i] = (node){in(), in()};
        a[i] = (node){a[i].x + a[i].y, a[i].x - a[i].y};
    }
    for (int i = 1; i <= m; ++i) {
        b[i] = (node){in(), in()};
        b[i] = (node){b[i].x + b[i].y, b[i].x - b[i].y};
    }
    work();
    std::swap(a, b);
    std::swap(n, m);
    std::swap(cntx, cnty);
    work();

    std::sort(cntx + 1, cntx + 1 + n);
    std::sort(cnty + 1, cnty + 1 + m);
    for (int i = 1; i <= n; ++i) {
        ans_min += 1ll * cntx[i] * (n - i);
        ans_min -= 1ll * cntx[i] * (cntx[i] - 1) / 2;
        ans_max += 1ll * cntx[i] * (i - 1);
        ans_max -= 1ll * cntx[i] * (cntx[i] - 1) / 2;
    }
    for (int i = 1; i <= m; ++i) {
        ans_min += 1ll * cnty[i] * (m - i);
        ans_min -= 1ll * cnty[i] * (cnty[i] - 1) / 2;
        ans_max += 1ll * cnty[i] * (i - 1);
        ans_max -= 1ll * cnty[i] * (cnty[i] - 1) / 2;
    }
    printf("%lld %lld\n", ans_min, ans_max);
    return 0;
}

T3、工厂

给定一个二分图,可以加边,使得二分图的所有极大匹配都是完美匹配。

\(Sol\)

先证明一件事:当且仅当这个二分图里的每个联通块左右点数相同且边都是连满的 \((A)\),满足题意 \((B)\)
\(B\)\(A\) 是显然的,不再赘述;
\(A\)\(B\) 可以用反证法:
若联通块中有两个异侧点 \(a\)\(b\) 之间没有连边,找到一条从 \(a\)\(b\) 的路径,把奇数边加入匹配边集,在此基础上找到一个极大匹配,找到的应该是完美匹配;将路径上奇数边取出匹配边集,偶数边加入,此时也是一个极大匹配但不是完美匹配。
\(QED\)

将几个联通块合并的代价为 \(( \sum x_i ) ^ 2\)
\(f_{s,i}\) 表示当前选出联通块集合为 \(s\),已经安排了点数和为 \(2i\) 的联通块 (使它们满足上述条件) 的最小代价。
转移有两种:

\[\begin{eqnarray} f_{s \bigcup \{ x \},i} &=& \min(f_{s \bigcup \{ x \}, i}, f_{S,i}) \notag \\ f_{s, i} &=& \min_{j = 0} ^ {i - 1} \{ f_{s,j} + (i - j) ^ 2 \} \ , (\Sigma_{x \in s} = \Sigma_{y \in s}) \notag \end{eqnarray} \]

由于状态数太多,这样也过不了,可以优化状压的方式;
我们只关心每一种联通块 (以左右点数分类) 有几个,可以用变进制数来优化状态数;
出题人告诉我们最多有 \(172032\) 种状态。

\(Source\)

#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
    int x = 0; char c = getchar(); bool f = 0;
    while (c < '0' || c > '9')
        f |= c == '-', c = getchar();
    while (c >= '0' && c <= '9')
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 35, inf = 0x3f3f3f3f;

int n, m, mp[N << 1][N << 1], tmp_x, tmp_y, init_edge, buc[N][N], pre[N << 1], f[172033][N], num[N << 1];
bool vis[N << 1];

struct node {
    int x, y;
    inline bool operator < (const node &b) const {
        return buc[this->x][this->y] < buc[b.x][b.y];
    }
    inline bool operator == (const node &b) const {
        return this->x == b.x && this->y == b.y;
    }
} a[N + N];

void prep(const int u) {
    vis[u] = 1;
    if (u <= n)
        ++tmp_x;
    else
        ++tmp_y;
    for (int i = 1; i <= mp[u][0]; ++i)
        if (!vis[mp[u][i]])
            prep(mp[u][i]);
}

int main() {
    //freopen("in", "r", stdin);
    freopen("factory.in", "r", stdin);
    freopen("factory.out", "w", stdout);
    n = in();
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j) {
            char c = getchar();
            while (c < '0' || c > '1')
                c = getchar();
            if (c == '1') {
                mp[i][++mp[i][0]] = j + n;
                mp[j + n][++mp[j + n][0]] = i;
            }
            init_edge += c == '1';
        }
    for (int i = 1; i <= n + n; ++i)
        if (!vis[i]) {
            tmp_x = tmp_y = 0;
            prep(i);
            ++buc[tmp_x][tmp_y];
        }

    for (int i = 0; i <= n; ++i)
        for (int j = 0; j <= n; ++j)
            if (buc[i][j])
                a[++m] = (node){i, j};
    std::sort(a + 1, a + 1 + m);
    m = std::unique(a + 1, a + 1 + m) - a - 1;
    pre[1] = 1;
    for (int i = 2; i <= m + 1; ++i)
        pre[i] = pre[i - 1] * (buc[a[i - 1].x][a[i - 1].y] + 1);

    memset(f, inf, sizeof(f));
    f[0][0] = 0;

    int nowx, nowy;
    for (int s = 0; s < (pre[m + 1]); ++s) {
        nowx = nowy = 0;
        for (int i = 1; i <= m; ++i)
            num[i] = s % pre[i + 1] / pre[i];
        for (int i = 1; i <= m; ++i)
            nowx += num[i] * a[i].x, nowy += num[i] * a[i].y;
        if (nowx == nowy)
            for (int i = 0; i < nowx; ++i)
                chk_min(f[s][nowx], f[s][i] + (nowx - i) * (nowx - i));
        for (int i = 1; i <= m; ++i)
            if (num[i] < buc[a[i].x][a[i].y])
                for (int j = 0; j <= n; ++j)
                    chk_min(f[s + pre[i]][j], f[s][j]);
    }
    printf("%d\n", f[pre[m + 1] - 1][n] - init_edge);

    return 0;
}

posted @ 2019-08-13 21:55  15owzLy1  阅读(172)  评论(1编辑  收藏  举报