「2018泉州国庆集训#2」 – 解题报告

A – 奇妙的棋盘

一定要思考,不能想当然。这句话是说给我听的。

把连通块连边,每一次 BFS 扩展就算做一次点击,然后\(O(n^2)\)确定路径长度,再按奇偶性判断就行了。

// A.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 75, MAX_P = 4910, INF = 0x3f3f3f3f;

int n, m, mem[MAX_P], head[MAX_P], current, tot, dep[MAX_P];
char opt[MAX_N][MAX_N];
bool mp[MAX_P][MAX_P], vis[MAX_P];

struct edge
{
    int to, nxt;
} edges[MAX_P << 3];

void addpath(int src, int dst)
{
    edges[current].to = dst, edges[current].nxt = head[src];
    head[src] = current++;
}

int getId(int x, int y) { return (x - 1) * m + y; }

int find(int x) { return x == mem[x] ? x : mem[x] = find(mem[x]); }

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%s", opt[i] + 1);
    for (int i = 1; i <= n * m; i++)
        mem[i] = i;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
            if (i > 1 && opt[i][j] == opt[i - 1][j])
                mem[find(getId(i - 1, j))] = mem[find(getId(i, j))];
            if (j > 1 && opt[i][j] == opt[i][j - 1])
                mem[find(getId(i, j - 1))] = mem[find(getId(i, j))];
        }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
            if (vis[find(getId(i, j))] == false)
                vis[find(getId(i, j))] = true, tot++;
            if (i > 1 && opt[i][j] != opt[i - 1][j] && !mp[find(getId(i, j))][find(getId(i - 1, j))])
                addpath(find(getId(i, j)), find(getId(i - 1, j))), addpath(find(getId(i - 1, j)), find(getId(i, j)));
            if (j > 1 && opt[i][j] != opt[i][j - 1] && !mp[find(getId(i, j))][find(getId(i, j - 1))])
                addpath(find(getId(i, j)), find(getId(i, j - 1))), addpath(find(getId(i, j - 1)), find(getId(i, j)));
        }
    int gans = INF;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
            if (find(getId(i, j)) != getId(i, j))
                continue;
            memset(dep, 0x3f, sizeof(dep));
            int id = getId(i, j), ans = 0;
            queue<int> q;
            q.push(id), dep[id] = 0;
            while (!q.empty())
            {
                int u = q.front();
                q.pop();
                for (int i = head[u]; i != -1; i = edges[i].nxt)
                    if (dep[edges[i].to] == INF)
                        q.push(edges[i].to), dep[edges[i].to] = dep[u] + 1, ans = max(ans, dep[edges[i].to]);
            }
            if (((ans & 1) && opt[i][j] == 'W') || ((ans & 1) == 0 && opt[i][j] == 'B'))
                ans++;
            gans = min(gans, ans);
        }
    printf("%d", gans);
    return 0;
}

B – 生命

曼哈顿距离转切比雪夫距离板子题。

转换好坐标之后,搞二维前缀和即可。

// B.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 2300, MAX_P = 500010, DOM = 1010;

int n, k, q, lb, ub, dist[MAX_N][MAX_N], pts[MAX_N * MAX_N][2];

inline int getId(int x, int y) { return (x - 1) * n + y; }

int main()
{
    scanf("%d%d%d", &n, &k, &q);
    for (int i = 1, x, y; i <= k; i++)
        scanf("%d%d", &x, &y), dist[x + y][x - y + n]++;
    for (int x = 1; x <= n; x++)
        for (int y = 1; y <= n; y++)
            pts[getId(x, y)][0] = x + y, pts[getId(x, y)][1] = x - y + n;
    for (int i = 1; i <= 2 * n; i++)
        for (int j = 1; j <= 2 * n; j++)
            dist[i][j] += dist[i - 1][j] + dist[i][j - 1] - dist[i - 1][j - 1];
    for (int s, i = 1; i <= q; i++)
    {
        scanf("%d", &s);
        if (s > n + 1)
            printf("%d\n", k);
        else
        {
            int ans = 0;
            for (int i = 0, x0, y0, x1, y1; i <= getId(n, n); i++)
            {
                x0 = max(pts[i][0] - s - 1, 0), y0 = max(pts[i][1] - s - 1, 0);
                x1 = min(pts[i][0] + s, n << 1), y1 = min(pts[i][1] + s, n << 1);
                ans = max(ans, dist[x1][y1] - dist[x0][y1] - dist[x1][y0] + dist[x0][y0]);
            }
            printf("%d\n", ans);
        }
    }
    return 0;
}

C – 死亡之树

这道题还是很好的。

考虑设计状态\(dp[vertexSet][leaveSet]\),总结点集合和叶子结点集合。每次转移的时候,都找一个新节点接到旧节点上。为了防止重复,我们保证新节点编号比旧节点大就行了。很好的一道题。

// C.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 11;

int mp[MAX_N][MAX_N], dp[1 << MAX_N][1 << MAX_N], n, m, k;

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1, x, y; i <= m; i++)
        scanf("%d%d", &x, &y), mp[x - 1][y - 1] = mp[y - 1][x - 1] = 1;
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++)
            if (mp[i][j])
                dp[(1 << i) | (1 << j)][(1 << i) | (1 << j)] = 1;
    for (int i = 1; i < (1 << n); i++)
        for (int j = 1; j < (1 << n); j++)
            if ((i & j) == j && dp[i][j] > 0 && __builtin_popcount(j) <= k)
                for (int p = 0; p < n; p++)
                    if ((i & (1 << p)) == 0)
                        for (int to = 0; to < n; to++)
                            if (mp[p][to] && (i & (1 << to)) && (j & (~(1 << to))) < (1 << p))
                                dp[i ^ (1 << p)][j & (~(1 << to)) ^ (1 << p)] += dp[i][j];
    int ans = 0;
    for (int stat = 1; stat < (1 << n); stat++)
        if (__builtin_popcount(stat) == k)
            ans += dp[(1 << n) - 1][stat];
    printf("%d", ans);
    return 0;
}

 

Leave a Reply

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