The 2023 ICPC Asia Hong Kong Regional Programming Contest

The 2023 ICPC Asia Hong Kong Regional Programming Contest

A. TreeScript

给你一个根,让你构造一棵树,每个节点被创造的时候必须知道它的父节点的地址和需要寄存器存放当前节点的地址,现在给定你每个节点之间的关系,并且现在根节点已经被创建,且有一个寄存器存放着根节点的地址,请问最少需要几个寄存器才能构造出这颗完整的树

题解:树形DP

我们先来分析一下样例:

  1. 0 1 2,ans = 1,代表:1>2>3,一开始1号根节点处有一个寄存器,那么创建节点2的时候,我们需要可以不需要多余的寄存器,我们只要将节点2的地址覆盖根节点处寄存器中存放的节点1的地址即可,也就是说节点2继承了节点1的寄存器,对于节点3同理,继承了节点2的寄存器

  2. 0 1 2 2 1 4 1,ans = 2

我们贪心的先创建小子树,发现我们在创建节点7的时候我们不能覆盖节点1的地址,也就是说我们不能继承节点1的寄存器,因为创建节点5和节点2的时候我们需要知道节点1的地址,所以我们创建节点7的时候需要新创建1个寄存器来存放节点7的地址;在创建节点5的时候我们可以继承节点7的寄存器,同理节点2也可以继承节点5的寄存器,所以我们发现我们贪心的创建从小子树到大子树的过程,大子树可以继承小子树的寄存器;对于节点2来说它还有子节点没有建立,所以我们需要建立2的子树,这个时候根节点1处的寄存器就没有用处了,可以用于创建节点3,然后创建节点4,再然后继承给节点6,这样一颗树就建完了,所以只用到了两个寄存器

通过模拟上述两个样例我们得知:

  1. 我们贪心从小子树的建立开始,到大子树结束,并且最后一颗最大的子树还能继承根节点的寄存器
  2. 如果根节点的子节点没有建立完,那么根节点处的寄存器不能被继承
  3. u节点为根,它需要的寄存器数量是建立次大子树v2需要的寄存器加上根节点上的寄存器(因为最后最大子树会继承两者之和)和建立最大子树v1需要的寄存器两者之间取max

状态表示:f[u]:以u为根节点的子树被创建所需的寄存器数量

状态属性:数量

状态转移:f[u]=max(f[v1],f[v2]+1),v1>=v2>=v3..

状态初始:f[u]=1

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;

int n;
vector<int> g[N];
int f[N];

void dfs(int u, int par)
{
    f[u] = 1;
    for (auto v : g[u])
    {
        if (v == par)
            continue;
        dfs(v, u);
        f[u] = max(f[u], f[v]);
    }
    int cnt = 0;
    for (auto v : g[u])
    {
        if (v == par)
            continue;
        if (f[u] == f[v])
            cnt++;
    }
    if (cnt > 1)
        f[u]++;
}

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
        g[i].clear();
    for (int i = 1, u; i <= n; ++i)
    {
        cin >> u;
        g[u].push_back(i);
        g[i].push_back(u);
    }
    dfs(1, 0);
    cout << f[1] << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

E. Goose, Goose, DUCK?

给定数组a,求有多少个不同区间,这些区间中每个不同数出现的次数不为k次,求出这些不同的区间数

题解:线段树维护最小值和最小值数

我们可以考虑初始为空数组,对于我们每次加入的数,我们将它看成右端点r,我们要找出对于当前右端点r来说[1,r1]中存在多少个合法的左端点l(合法:区间[l,r]中每个不同数出现的次数不为k次),我们在模拟时发现对于任意元素x[1,r]出现的位置p1,p2,p3...pmm>=k,那么对于这个数x而言不合法的区间为[pmk+1,pmk+1],并且我们发现每次在添加一个新的数x进入数组后,如果原本x[1,r]出现的最后位置pmm>=k,那么会撤销x之前的不合法区间,产生新的不合法区间

例如1,2,2,k=2,现在对于2来说不合法区间为[1,2],如果我们再添加一个2后,数组变为1,2,2,2,k=2,那么现在[1,2]变成了合法区间,产生了新的不合法区间[3,3],所以我们可以把问题简化为撤销不合法区间看作区间1,产生新的合法区间看成区间+1,然后答案即为0的数量

所以我们只需要使用线段树维护最小值和最小值的个数,然后如果区间的最小值为0,我们对答案就可以加上这段区间最小值0的个数;同时对于任意元素x出现的位置和个数我们可以利用邻接表的思想实现

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 1e6 + 10, M = 4e5 + 10;

int n, k;
int a[N];
vector<int> v[N];
struct info
{
    int cnt;
    int minn;
};
struct node
{
    int lazy, len;
    info val;
} seg[N << 2];
info operator+(const info &a, const info &b)
{
    info c;
    c.minn = min(a.minn, b.minn);
    c.cnt = 0;
    if (c.minn == a.minn)
        c.cnt += a.cnt;
    if (c.minn == b.minn)
        c.cnt += b.cnt;
    return c;
}
void settag(int id, int tag)
{
    seg[id].val.minn += tag;
    seg[id].lazy += tag;
}
void up(int id)
{
    seg[id].val = seg[id << 1].val + seg[id << 1 | 1].val;
}
void down(int id)
{
    if (seg[id].lazy == 0)
        return;
    settag(id << 1, seg[id].lazy);
    settag(id << 1 | 1, seg[id].lazy);
    seg[id].lazy = 0;
}
void build(int id, int l, int r)
{
    seg[id].len = r - l + 1;
    if (l == r)
    {
        seg[id].val.minn = 0;
        seg[id].val.cnt = 1;
        seg[id].lazy = 0;
        return;
    }
    int mid = (l + r) >> 1;
    build(id << 1, l, mid);
    build(id << 1 | 1, mid + 1, r);
    up(id);
}
void modify(int id, int l, int r, int ql, int qr, int val)
{
    if (ql <= l && r <= qr)
    {
        settag(id, val);
        return;
    }
    down(id);
    int mid = (l + r) >> 1;
    if (qr <= mid)
        modify(id << 1, l, mid, ql, qr, val);
    else if (ql > mid)
        modify(id << 1 | 1, mid + 1, r, ql, qr, val);
    else
    {
        modify(id << 1, l, mid, ql, qr, val);
        modify(id << 1 | 1, mid + 1, r, ql, qr, val);
    }
    up(id);
}
info query(int id, int l, int r, int ql, int qr)
{
    if (ql <= l && r <= qr)
    {
        return seg[id].val;
    }
    down(id);
    int mid = (l + r) >> 1;
    if (qr <= mid)
        return query(id << 1, l, mid, ql, qr);
    else if (ql > mid)
        return query(id << 1 | 1, mid + 1, r, ql, qr);
    else
        return query(id << 1, l, mid, ql, qr) + query(id << 1 | 1, mid + 1, r, ql, qr);
}

void solve()
{
    cin >> n >> k;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    build(1, 1, n);
    int ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        if (v[a[i]].size() >= k)
        {
            int l, r;
            if (v[a[i]].size() == k)
                l = 1, r = v[a[i]][v[a[i]].size() - k];
            else
            {
                l = v[a[i]][v[a[i]].size() - k - 1] + 1;
                r = v[a[i]][v[a[i]].size() - k];
            }
            // cout << l << ' ' << r << endl;
            modify(1, 1, n, l, r, -1);
        }
        v[a[i]].push_back(i);
        if (v[a[i]].size() >= k)
        {
            int l, r;
            if (v[a[i]].size() == k)
                l = 1, r = v[a[i]][v[a[i]].size() - k];
            else
            {
                l = v[a[i]][v[a[i]].size() - k - 1] + 1;
                r = v[a[i]][v[a[i]].size() - k];
            }
            modify(1, 1, n, l, r, 1);
        }
        if (query(1, 1, n, 1, i).minn == 0)
            ans += query(1, 1, n, 1, i).cnt;
    }
    cout << ans << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

K. Maximum GCD

给定一个长度为n的数组,如果这个数组中所有元素的gcd越大,那么这个数组就越漂亮,现在可以进行任意次操作,每次操作可以对任意一个元素进行取模,然后使得最后数组gcd最大,并求出最大gcd

题解:思维

引理:对于任意数x进行取模,如果x是偶数,那么x取模后为[0,x/21],如果x是奇数,那么x取模后为[0,x/2]

我们发现gcd最大值小于等于该数组中最小元素x

  1. 如果所有的数取模后的范围中都包含x,那么最大值一定是x
  2. 如果所有数都是x的倍数,那么最大值也一定是x
  3. 如果不是以上情况那么最后答案一定是x2
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;

int n;

void solve()
{
    cin >> n;
    vector<int> a;
    for (int i = 1; i <= n; ++i)
    {
        int x;
        cin >> x;
        a.push_back(x);
    }
    sort(all(a));
    int minn = INF;
    bool flag = true;
    for (int i = 1; i < a.size(); ++i)
    {
        if (a[i] % a[0] != 0)
            flag = false;
        minn = min(a[i] / 2, minn);
    }
    if (minn >= a[0] || flag == true)
        cout << a[0] << endl;
    else
    {
        cout << a[0] / 2 << endl;
    }
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

L. Permutation Compression

给定一个排列a,通过执行k次以下操作回答是否可以将其转换为另一个序列b,每次可以选择一个长度为li的区间并删除该区间中的最大元素

题解:贪心 + set动态维护左右两边最大值 + 树状数组计数

经过模拟我们发现我们可以贪心的在排列a中从大到小删除元素,我们发现一个数x如果能被当成最大值被删除的区间为x左边第一个比x大的数的位置和x右边第一个比x大的数的位置形成的区间[maxl,maxr],但是我们不能用单调栈实现,因为整个删除过程是动态的,所以我们考虑利用set动态维护x左右两边最大值的位置(因为我们是从大到小删除元素的,所以对于x来说,如果比x大的元素没有被删除,那么它的位置一定会出现在set中,那么对于x来说,我们就可以利用set二分出比x大的元素的位置形成区间[maxl,maxr]),我们只要利用树状数组计算出在这个区间中一共有多少个未被删除的数即可,这样就动态维护出了x作为最大值所在区间的长度lenlen即为该区间中未被删除的数,然后如果给定的操作中存在li<=len,代表有操作可以实现删除x,反之则代表无法还原成序列b

存在许多边界,比较难调,重点了解思路

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;

int n, m, k;
int a[N], b[N];
int pos[N];
int c[N];
multiset<int> st;

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

void add(int x, int val)
{
    while (x <= n)
    {
        c[x] += val;
        x += lowbit(x);
    }
}

int getsum(int x)
{
    int sum = 0;
    while (x > 0)
    {
        sum += c[x];
        x -= lowbit(x);
    }
    return sum;
}

void solve()
{
    st.clear();
    cin >> n >> m >> k;
    vector<int> vis(n + 10);
    for (int i = 1; i <= n; ++i)
        c[i] = 0;
    for (int i = 1; i <= n; ++i)
    {
        cin >> a[i];
        pos[a[i]] = i;
    }
    for (int i = 1; i <= m; ++i)
    {
        cin >> b[i];
        vis[b[i]] = 1;
    }
    for (int i = 1; i <= k; ++i)
    {
        int len;
        cin >> len;
        st.insert(len);
    }
    for (int i = 1, j = 1; i <= m; ++i)
    {
        while (j <= n && a[j] != b[i])
            j++;
        if (j > n)
        {
            cout << "NO" << endl;
            return;
        }
    }
    for (int i = 1; i <= n; ++i)
        add(i, 1);
    set<int> st2;
    st2.insert(0);
    st2.insert(n + 1);
    for (int i = n; i >= 1; i--)
    {
        if (vis[i])
        {
            st2.insert(pos[i]);
            continue;
        }
        auto it2 = st2.lower_bound(pos[i]);
        int l = *(prev(it2)), r = *it2;
        int len = getsum(r - 1) - getsum(l);
        add(pos[i], -1);
        auto it = st.upper_bound(len);
        if (it == st.begin())
        {
            cout << "NO" << endl;
            return;
        }
        st.erase(--it);
    }
    cout << "YES" << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}
posted @   Zeoy_kkk  阅读(1731)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
点击右上角即可分享
微信分享提示