2022“杭电杯”中国大学生算法设计超级联赛(8)

本文解析了2022年“杭电杯”中国大学生算法设计超级联赛第八场的六道题目,包括直线覆盖点、序列翻转、长方形划分等,提供了详细的思路与代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2022“杭电杯”中国大学生算法设计超级联赛(8)

[题目链接](Search Result (hdu.edu.cn))

D Quel’Thalas

题目大意

在二维平面上,[0,n]*[0,n]的整数点中去掉(0,0)之后,最少需要多少条直线使得每个整数点至少有一条边覆盖。

题解

易得平行于x轴的n条直线和平行于y轴的n条直线即可满足上述条件,且此为最少的直线数量。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 5;
int t, n;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        cin >> n;
        cout << 2 * n << endl;
    }
    return 0;
}

A Theramore

题目大意

有一个01序列,可以任意翻转奇数长度的区间,求能达到的最小字典序。

题解

使用长度为 3 的翻转,那么位置奇偶性相同的位置可以随便换。对奇数位置和偶数位置,分别把 0 放到前面,1 放到后面即可。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 5;
int t, n, id1, id2;
string s;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        cin >> s;
        n = s.size();
        vector<char> v1, v2;
        id1 = id2 = 0;
        for (int i = 0; i < n; i++)
        {
            if (i % 2)
                v1.push_back(s[i]);
            else
                v2.push_back(s[i]);
        }
        sort(v1.begin(), v1.end());
        sort(v2.begin(), v2.end());
        for (int i = 0; i < n; i++)
        {
            if (i % 2)
                cout << v1[id1++];
            else
                cout << v2[id2++];
        }
        cout << endl;
    }
    return 0;
}

K Stormwind

题目大意

一个n*m的长方形,可以沿水平或竖直方向画若干条线,每条线的两端点都在长方形的边界上。要求
这些线划分出的每个小长方形面积都>=k,求最多可以画几条线。

题解

二分。

checked函数:因为每一个小长方形的面积都需要大于等于k,所以划分的时候应该尽量平均划分,所以只需要求最小的长方形的面积是否大于等于k即可,而最小的长方形的边长就是n / (i + 1),即长度除以划分的线的数量。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 1e2 + 5;
const ll mod = 1e9 + 7;
ll t, n, m, k;
bool checked(ll mid)
{
    ll x, y, j;
    for (ll i = 0; i <= mid; i++)
    {
        j = mid - i;
        x = n / (i + 1), y = m / (j + 1);
        if (x * y >= k)
            return 1;
    }
    return 0;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        cin >> n >> m >> k;
        ll l = 0, r = n + m - 2, mid, ans;
        while (l <= r)
        {
            mid = (l + r) / 2;
            if (checked(mid))
            {
                ans = mid;
                l = mid + 1;
            }
            else
                r = mid - 1;
        }
        cout << ans << endl;
    }
    return 0;
}

H Orgrimmar

题目大意

求一棵树的最大分离集的大小,分离集中每个点至多有一条边与分离集中另一个点相连。

题解

树形dp。

定义dp数组中,父节点u及其子节点v的状态:dp[u] [0]表示u不选择;dp[u] [1]表示u选择,v不选择;dp[u] [2]表示u选择,且v选择。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 5;
const int Max = 0x3f3f3f3f;
int t, n;
vector<int> g[maxn];
int dp[maxn][5];
void dfs(int u, int fa)
{
    int x0 = 0, x1 = 0, x2 = -Max;
    for (auto v : g[u])
    {
        if (v == fa)
            continue;
        dfs(v, u);
        x0 += max({dp[v][0], dp[v][1], dp[v][2]});
        x1 += dp[v][0];
        x2 = max(x2, dp[v][1] - dp[v][0]);
    }
    dp[u][0] = x0;
    dp[u][1] = x1 + 1;
    dp[u][2] = x2 + x1 + 1;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int size(512 << 20);
    __asm__("movq %0, %%rsp\n" ::"r"((char *)malloc(size) + size));
    cin >> t;
    while (t--)
    {
        cin >> n;
        for (int i = 1; i <= n; i++)
            g[i].clear();
        for (int i = 1; i < n; i++)
        {
            int u, v;
            cin >> u >> v;
            g[u].push_back(v);
            g[v].push_back(u);
        }
        dfs(1, 0);
        cout << max({dp[1][0], dp[1][1], dp[1][2]}) << endl;
    }
    exit(0);
}

E Ironforge

题目大意

一条链,每个点上有一个数ai,每条边上有一个质数bi。一开始在某个点上,有一个空背包,走到一个
点上可以把它的质因子放进背包,一条边如果背包里有那个质数就可以走。多组询问求从 x 出发能否走
到 y。

题解

[大佬链接](2022 杭电多校(8) 个人题解 更新至6题 - 知乎 (zhihu.com))

讲的超级清楚!!!太牛了!!!

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
int t, n, m;
int a[maxn], b[maxn];
int l[maxn], r[maxn], minp[maxn];
vector<int> pos[maxn];
void init()
{
    for (int i = 1; i <= 200000; i++)
        pos[i].clear();
    for (int i = 1; i <= n; i++)
    {
        while (a[i] > 1)
        {
            int p = minp[a[i]];
            while (a[i] % p == 0)
                a[i] /= p;
            pos[p].push_back(i);
        }
    }
}
bool checked(int p, int l, int r)
{
    if (pos[p].size() == 0)
        return 0;
    return upper_bound(pos[p].begin(), pos[p].end(), r) - lower_bound(pos[p].begin(), pos[p].end(), l);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    for (int i = 2; i <= 200000; i++)
    {
        if (minp[i])
            continue;
        for (int j = i; j <= 200000; j += i)
            if (!minp[j])
                minp[j] = i;
    }
    cin >> t;
    while (t--)
    {
        cin >> n >> m;
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        for (int i = 1; i < n; i++)
            cin >> b[i];
        init();
        for (int i = n; i >= 1; i--)
        {
            r[i] = i;
            while (r[i] < n && checked(b[r[i]], i, r[i]))
            {
                r[i] = r[r[i] + 1];
            }
        }
        for (int i = 1; i <= n; i++)
        {
            l[i] = i;
            if (i > 1 && r[i - 1] >= i)
            {
                if (checked(b[i - 1], i, r[i]))
                {
                    l[i] = l[i - 1];
                    r[i] = r[i - 1];
                }
            }
            else
            {
                bool flag = 1;
                while (flag)
                {
                    flag = 0;
                    while (l[i] > 1 && checked(b[l[i] - 1], l[i], r[i]))
                    {
                        flag = 1;
                        l[i] = l[l[i] - 1];
                    }
                    while (r[i] < n && checked(b[r[i]], l[i], r[i]))
                    {
                        flag = 1;
                        r[i] = r[r[i] + 1];
                    }
                }
            }
        }
        for (int i = 1; i <= m; i++)
        {
            int x, y;
            cin >> x >> y;
            if (y >= l[x] && y <= r[x])
                cout << "Yes" << endl;
            else
                cout << "No" << endl;
        }
    }
    return 0;
}

G Darnassus

题目大意

给出一个排列p,把每个位置视为点,建一个无向图,i,j之间的边权为|i-j|*|pi-pj|。求这个图的最小生成树。

题解

依次连接i和i+1,每条边的边权等于|p[i+1]-p[i]|<=n-1(因为p数组是排列),且有n-1条,因此最小生成树中的每条边的边权一定<=n-1。

边权为|i-j|*|pi-pj|,意味着|i-j|和|pi-pj|必有至少一个<=sqrt(n-1),因此需要找到所有这样的边,然后使用Kruskal 算法求出最小生成树即可。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e4 + 5;
const int maxm = 5e7 + 5;
int t, n, cnt, num, ans;
ll res;
int a[maxn], pos[maxn], head[maxn];
int f[maxn], r[maxn];
struct Edge
{
    int u, v, next;
} e[maxm];
void init()
{
    cnt = res = ans = 0;
    num = sqrt(n);
    memset(head, -1, sizeof head);
    memset(r, 0, sizeof r);
    for (int i = 1; i <= n; i++)
        f[i] = i;
}
int find(int i)
{
    if (f[i] == i)
        return i;
    return f[i] = find(f[i]);
}
void unionn(int i, int j)
{
    int i_fa = find(i);
    int j_fa = find(j);
    if (i_fa != j_fa)
    {
        if (r[i_fa] < r[j_fa])
            swap(i_fa, j_fa);
        f[j_fa] = i_fa;
        if (r[i_fa] == r[j_fa])
            r[i_fa]++;
    }
}
void add(int u, int v, int w)
{
    e[cnt].u = u;
    e[cnt].v = v;
    e[cnt].next = head[w];
    head[w] = cnt++;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        cin >> n;
        init();
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i];
            pos[a[i]] = i;
        }
        for (int i = 1; i <= n; i++)
        {
            for (int j = i + 1; j <= n && j <= i + num; j++)
            {
                int w = abs(i - j) * abs(a[i] - a[j]);
                if (w < n)
                    add(i, j, w);
            }
        }
        for (int i = 1; i <= n; i++)
        {
            for (int j = i + 1; j <= n && j <= i + num; j++)
            {
                int w = abs(i - j) * abs(pos[i] - pos[j]);
                if (w < n)
                    add(pos[i], pos[j], w);
            }
        }
        for (int i = 0; i <= n - 1; i++)
        {
            for (int j = head[i]; j != -1; j = e[j].next)
            {
                int u = e[j].u, v = e[j].v;
                if (find(u) == find(v))
                    continue;
                unionn(u, v);
                res += i;
                if (++ans == n - 1)
                    break;
            }
            if (ans == n - 1)
                break;
        }
        cout << res << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值