[竞赛地址] https://ac.nowcoder.com/acm/contest/82345

本来想参加vp的,有事耽误了。

老规矩,先上题面,后上题解。做题顺序按提交人数来(从易到难)。

A. How Many Permutation?

给定一个长度为n的数组和数字k,问有多少排列,满足

  • 任意两个相邻元素的差值都不小于k。

输出满足上述条件的、排列的个数。

I. A Math Problem

给定一个数组,q个查询,每次输入x,求表达式

在这里插入图片描述

F. Sequence

在这里插入图片描述
给定一个序列a,可以执行两种操作。

  • 删除序列中的元素。
  • 在序列中的元素前插入0。

可以执行上述操作任意次。问最多能使多少位置满足a[i]=i。

B. Ternary Tree

在这里插入图片描述
给定一个满三叉树,深度为n,有q次操作,每次输入节点k,表示删除k及其子树。求每次操作后的三叉树剩余节点数量。

n<=25

C. Good Partition

给定一组数,将其分成2个子数组,使得对于每个子数组,满足以下:

  • 任意取数组中两个元素,它们的和不构成完全平方数

问是否存在这种划分,输出YES或NO

A. How Many Permutation?(签到)

直接暴力统计,可以用dfs枚举排列。

这里图方便,用了 next_permutation

int n, k;
void solve() {
    scanf("%d%d", &n, &k);
    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
    }
    sort(a.begin(), a.end());
    int ans = 0;
    do {
        bool ok = 1;
        for (int i = 0; i < n - 1; ++i) {
            if (abs(a[i] - a[i+1]) < k) {
                ok = 0;
                break;
            }
        }
        ans += ok;
    } while (next_permutation(a.begin(), a.end()));
    printf("%d\n", ans);
}

I. A Math Problem(签到)

预处理平方和和单次方和,注意开long long

int n, q, x;
void solve() {
    scanf("%d%d", &n, &q);
    vector<int> a(n);
    ll p2 = 0, p1 = 0;
    for (int i = 0; i < n; ++i) {
        scanf("%d", &x);
        
        p2 += 1ll * x * x;
        p1 += x;
    }
    
    while (q--) {
        scanf("%d", &x);
        printf("%lld\n", p2 + (-2ll) * p1 * x + 1ll * n * x * x);
    }
    
}

F. Sequence(dp/最长上升子序列)

套了个壳,实际就是经典的最长上升子序列

使用贪心+二分,维护每个长度为i的子序列,使其末尾元素最小化,

int n, q, x;
void solve() {
    scanf("%d", &n);
    vector<int> a(n + 1);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
    }
    vector<int> dp(n + 1);
    int len = 1, pos;
    dp[1] = a[1];
    for (int i = 2; i <= n; ++i) {
        if (a[i] > dp[len]) {
            dp[++len] = a[i];
        } else {
            pos = lower_bound(dp.begin() + 1, dp.begin() + len + 1, a[i]) - dp.begin();
            dp[pos] = a[i];
        }
    }
    printf("%d\n", len);
    
}

B. Ternary Tree(二分/模拟)

维护每个节点的被删除数。具体实现看代码,有详细注释

数据卡常比较严格,用空间需要省点。。

const int maxn = 27;
#define ll long long

ll n, m, k;
// map<ll, ll> del;
// map<ll, bool> vis; 
unordered_map<ll, ll> del; // del[k]表示节点k下的子树被删除的节点数
unordered_map<ll, bool> vis; // vis[k]标记k是否被删除
ll p[maxn]; // 3^i
// ll mn[maxn], mx[maxn]; // 每一层的 边界值
ll init() {
//     mn[0] = mx[0] = 1;
//     for (int i = 1; i < n; ++i) {
//         mn[i] = mn[i-1] * 3 - 1;
//         mx[i] = mx[i-1] * 3 + 1;
//     }
    
    p[0] = 1;
    ll res = p[0];
    for (int i = 1; i < n; ++i) {
        p[i] = p[i-1] * 3;
//         mn[i] = mn[i-1] * 3 - 1;
//         mx[i] = mx[i-1] * 3 + 1;
        res += p[i];
    }
    p[n] = p[n-1] * 3;
    return res;
}
ll cal(ll d) { // 求出深度d的子节点个数
    return (p[n-d+1] - 1) / 2;
}

ll get_deep(ll k) { // 求出k所在深度
//     for (int i = 0; i < n; ++i) {
//         if (mn[i] <= k && k <= mx[i]) {
//             return i + 1;
//         }
//     }
//     return n;
    
    ll l = 1, r = n, pos = 0;
    ll mid;
    while (l <= r) {
        mid = (l + r) / 2;
        ll num = ((p[mid] - 1) / 2);
        
        if (num < k) {
            pos = max(pos, mid);
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
    return pos + 1;
}

bool check(ll k) { // 判断k是否被删除
    while (k) { // 整体思路就是找祖先节点是否存在被删除的
        if (vis[k]) {
            return true;
        }
        k = (k + 1) / 3;
    }
    return false;
}
void solve() {
    scanf("%lld%lld", &n, &m);
    del.clear();
    vis.clear(); // 
    ll all = init();
    ll ans = all; //
    while (m--) {
        scanf("%lld", &k);
        if (check(k)) { //
            printf("%lld\n", ans);
            continue;
        }
        vis[k] = 1;
        
        ll d = get_deep(k); // 求出k所在深度
        ll num = cal(d); // 求出深度d的子节点个数
        ll res = num - del[k]; // 本次新增 删除数量。
        
        while (k) { // 更新祖先节点的 被删除数
            del[k] += res;
            k = (k + 1) / 3;
        }
        
        ans = all - del[1];
        printf("%lld\n", ans);
    }
    
}

C. Good Partition(图论/二分图/染色)

将能构成完全平方数的数对连边,预处理求出所有关联的边。

判断图是否可以用2种颜色进行染色。

const int maxn = 100001;

int n, mx, m, k;
bool ok;
void solve() {
    scanf("%d", &n);
    mx = 0;
    vector<bool> vis(maxn*2); // 标记
    vector<int> a(n + 1);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        vis[a[i]] = 1;
        mx = max(mx, a[i]);
    }
    
    vector<vector<int>> ve(mx + 1); // 图
    vector<int> color(mx + 1, -1); // 染色
    m = sqrt(2 * mx) + 1;
    for (int i = 1; i <= n; ++i) {
        int &x = a[i];
        for (int j = m; j >= 1; --j) {
            if (j * j < x) {
                break;
            }
            int y = j * j - x;
            if (x != y && y <= mx && vis[y]) {
                ve[x].push_back(y);
            }
        }
    }
    
    function<void(int, int, int)>  dfs = [&](int u, int p, int c) {
        if (!ok) {
            return;
        }
        
        if (color[u] != -1) {
            if (color[u] != c) {
                ok = 0;   
            }
            return;
        }
        color[u] = c;

        for (auto v: ve[u]) {
            if (v == p) {
                continue;
            }
            dfs(v, u, 1 - c);
        }
    };
    
    ok = 1;
    for (int i = 1; i <= n && ok; ++i) {
        int &x = a[i];
        if (color[x] == -1) {
            dfs(x, -1, 0);
        }
    }
    printf("%s\n", ok ? "YES" : "NO");
}

对方正在debug
1 声望0 粉丝