团体程序设计天梯赛(L3)

肿瘤诊断

思路

找到每个连通块,如果连通块大小不小于 T T T,累加进总和即可。
可以用并查集维护连通块,但是因为这里的连通块是三维的,直接维护有点麻烦,不然就需要转换为一维的下标。
也可以直接从 ′ 1 ′ '1' 1 开始六个方向搜索,把每搜完一个连通块,就把该连通块的点全部标记,之后遇到该连通块的点就不进行搜索。

细节

注意,搜索用 d f s dfs dfs 会爆栈,请使用 b f s bfs bfs

复杂度

令三个维度的上界分别为 N , M , K N,M,K N,M,K,则最坏空间复杂度和时间复杂度均为 O ( N ∗ M ∗ K ) O(N*M*K) O(NMK)

代码实现

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int N = 2000, M = 200, K = 70;

int n, m, k, v, s;
int g[K][N][M], st[K][N][M];
int dx[] = { -1, 1, 0, 0, 0, 0 };
int dy[] = { 0, 0, -1, 1, 0, 0 };
int dz[] = { 0, 0, 0, 0, -1, 1 };
// 六个方向的方向偏移量

int cnt;

void dfs(int x, int y, int z)
{
    st[z][x][y] = 1;
    cnt++;
    for (int i = 0; i < 6; i++) {
        int xi = x + dx[i];
        int yi = y + dy[i];
        int zi = z + dz[i];
        if (xi < 1 || xi > n || yi < 1 || yi > m
            || zi < 1 || zi > k || st[zi][xi][yi] || !g[zi][xi][yi])
            continue;
        dfs(xi, yi, zi);
    }
}

void bfs(int xo, int yo, int zo)
{
    queue<array<int, 3>> q;
    q.push({ xo, yo, zo });
    while (q.size()) {
        auto it = q.front();
        q.pop();
        int x = it[0], y = it[1], z = it[2];
        st[z][x][y] = 1;
        // st[z][x][y] 标记 (z,x,y) 点是否走过
        cnt++;
        for (int i = 0; i < 6; i++) {
            int xi = x + dx[i];
            int yi = y + dy[i];
            int zi = z + dz[i];
            if (xi < 1 || xi > n || yi < 1 || yi > m
                || zi < 1 || zi > k || st[zi][xi][yi] || !g[zi][xi][yi])
                continue;
            st[zi][xi][yi] = 1;
            q.push({ xi, yi, zi });
        }
    }
}

signed main()
{
    cin >> n >> m >> k >> v;
    for (int z = 1; z <= k; z++) {
        for (int x = 1; x <= n; x++) {
            for (int y = 1; y <= m; y++) {
                cin >> g[z][x][y];
            }
        }
    }
    for (int z = 1; z <= k; z++) {
        for (int x = 1; x <= n; x++) {
            for (int y = 1; y <= m; y++) {
                if (!st[z][x][y] && g[z][x][y]) {
                    cnt = 0;
                    // cnt 为搜索后找到的连通块大小
                    bfs(x, y, z);
                    if (cnt >= v)
                        s += cnt;
                }
            }
        }
    }
    cout << s << '\n';
}

是否完全二叉搜索树

思路

当二叉树为满二叉树时,按从上到下,从左到右对节点进行编号,对于编号为 u u u 的节点,其左儿子为 u ∗ 2 u*2 u2,右儿子为 u ∗ 2 + 1 u*2+1 u2+1,所以按照这样的规律建立根节点编号为 1 1 1 的二叉搜索树。
(注意,这样建树的话,因为深度最大为 20 20 20,节点最大编号为 1 0 6 10^6 106,数组大小至少大于 1 0 6 10^6 106。)

层序遍历

直接从根节点 1 1 1 开始进行 b f s bfs bfs 即可。

判断是否为满二叉树

树的深度从 0 0 0 开始向下递增,当深度为 i i i 时,对应层的最大节点数为 2 i 2^i 2i,可以在建树之后统计每一层的节点数。
假设最深深度为 m a ma ma ,那么若深度为 i i i,且 i < m a i<ma i<ma,对应层应该满足节点数为 2 i 2^i 2i,而深度为 m a ma ma 对应的层应该满足节点从最左段开始向右连续排列。
m a ma ma 层的最左端节点编号为 2 m a 2^{ma} 2ma,最右端节点编号为 2 m a + 1 − 1 2^{ma+1}-1 2ma+11,因此可以从左到右遍历计算有节点的连续段和无节点的连续段的段数。
如果存在其中一种段数大于一的则不合法,因为最多就是两种各一段;同时,如果存在有节点的连续段,其一定从最左端开始,若最左端无节点而有存在有节点的连续段也是不合法的。

复杂度

空间复杂度和时间复杂度均为 O ( 2 n ) O(2^n) O(2n)

代码实现

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 5;

int n, ma;
int v[N], cnt[N];
// 注意深度最大为20,因此建树的最大编号为10^6

// u为节点编号,x为插入数字,d为当前深度
void insert(int u, int x, int d)
{
    // v[u]=0则未被赋值,同时也说明节点i不存在
    if (!v[u]) {
        v[u] = x;
        cnt[d]++;
        // 对应深度的节点数加一
        ma = max(ma, d);
    } else {
        // 大的插入左边,否则插入右边
        if (x > v[u]) {
            insert(u * 2, x, d + 1);
        } else {
            insert(u * 2 + 1, x, d + 1);
        }
    }
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        insert(1, x, 0);
    }

    int f = 0;
    queue<int> q;
    q.push(1);
    while (q.size()) {
        int u = q.front();
        q.pop();
        if (f)
            cout << ' ';
        cout << v[u];
        f = 1;
        if (v[u * 2])
            q.push(u * 2);
        if (v[u * 2 + 1])
            q.push(u * 2 + 1);
    }
    cout << '\n';

    int flag = 1;
    for (int i = 0; i < ma; i++) {
        int sz = 1 << i;
        if (cnt[i] != sz)
            flag = 0;
    }
    int f1 = 0, f2 = 0, l = 1 << ma, r = (1 << (ma + 1)) - 1;
    for (int i = l; i <= r; i++) {
        int j = i;
        // v[i]=0 对应不存在节点,否则存在
        while (j <= r && (v[i] != 0) == (v[j] != 0)) {
            j++;
        }
        if (v[i] != 0)
            f1++;
        else
            f2++;
        // 统计对应段数数量
        i = j - 1;
    }
    // 存在两段或者以上的情况 和 有节点段不是从最左边开始的情况
    if (f1 > 1 || f2 > 1 || (!v[l] && f1))
        flag = 0;

    cout << (flag ? "YES" : "NO");
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值