The 3rd Universal Cup. Stage 20: Kunming (2024昆明区域赛MJHLGC)

2024昆明区域赛算法解析

M. Matrix Construction

知识点:构造。

分析:构造题就要多找一些特殊的情况,观察题目中给出的要求的性质,发现和对角线似乎有一些神秘的关系,每形成两个数和的两个点一定是位于不同的对角线上,主对角线或副对角线都是可以构造的啦,按照对角线顺序从小到大排列得到最后的结果。

#include <bits/stdc++.h>
using namespace std;
#define x first
#define y second
#define ll long long
#define PII pair<int, int>
const int N = 1010;
int ans[N][N];
void solve()
{
    int n, m;
    cin >> n >> m;
    cout << "YES" << endl;
    // ( i , j )  在的对角线是哪一个
    int cnt = 1;
    for (int i = 1; i <= n + m - 1; i++)
    {
        if (i <= m)
        {
            int x = 1, y = i;
            while (x <= n && y >= 1)
            {
                ans[x][y] = cnt++;
                x++, y--;
            }
        }
        else
        {
            int x = i - m + 1, y = m;
            while (x <= n && y >= 1)
            {
                ans[x][y] = cnt++;
                x++, y--;
            }
        }
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            cout << ans[i][j] << " ";
        }
        cout << endl;
    }
}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

J. Just another Sorting Problem

知识点:分类讨论

分析:经验题,自然可以看出n = 2 , n = 3是两个特殊的情况需要拿出来考虑,其他的n >= 4只要不能让A一次获胜就B胜。

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define x first
#define y second
#define ll long long
#define PII pair<int, int>

void solve()
{
    int n;
    string name;
    cin >> n >> name;
    vector<int> a(n + 1);
    int cnt = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        if (i != a[i])
            cnt++;
    }
    // 无论如何操作A都获胜
    if(n == 2){
        cout << "Alice" << endl;
        return;
    }
    // 分情况讨论
    if(n == 3){
        if(cnt == 3){
            if(name == "Bob")
                cout << "Alice" << endl;
            else
                cout << "Bob" << endl;
        }else if(cnt == 0 ){
            if(name == "Bob"){
                cout << "Alice" << endl;
            }else{
                cout << "Bob" << endl;
            }
        }else{
            if(name == "Bob"){
                cout << "Bob" << endl;
            }else
            {
                cout << "Alice" << endl;
            }
        }
        return;
    }
    // n >= 4 的时候,只要A不能一步胜利则就不可能再胜利注意cnt>=2这个前置条件。
    if (cnt == 2 && name == "Alice")
        cout << "Alice" << endl;
    else
        cout << "Bob" << endl;

}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

H. Horizon Scanning

知识点:计算几何

分析:要主要到一个正难则反的问题,如果正着显然计算量会爆炸,就逆着去想看会不会简单一点,发现找到最小的满足所有角度都包含k个点就是去找到包含n-k个点的最小角度是多少,再2*pi减去这个最小角度即可。

注意:还有注意处理循环的条件时,我们可以把所有的角度都 at + 2 *pi ,这样可以处理跨越2 *pi 的角度。

#include <bits/stdc++.h>
#define endl  "\n"
using namespace std;
const int N = 200010;

int n, k;

const float pi = 3.1415926546;
struct Pot
{
    int x, y;
} points[N];

float v[N];

void solve()
{
    int cnt = 0;
    cin >> n >> k;
    for (int i = 0; i < n; i++)
    {
        cin >> points[i].x >> points[i].y;
        float at = atan2(points[i].y, points[i].x);
        v[cnt ++ ] = at; // 存角度
        v[cnt ++ ] = at + 2 * pi;
    }
    sort(v , v + cnt );
    // 对于任何的角度能都扫到 k个
    float mn = pi * 2; // 找到最小的包含 n - k个点的区域
    for (int i = 0; i < n ; i++)
    {
        mn = min(mn, v[i + n - k] - v[i]);
    }
    cout << fixed << setprecision(6) << 2 * pi - mn << endl;
}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

L. Last Chance: Threads of Despair

知识点:贪心

分析:这个题目的难点在于想清楚爆炸是不是越多越好以及,总的攻击次数是多少,攻击次数为什么能够都放在前面,想清楚这个就可以从两方从小到大指针枚举了,看能不能使r到达最后的终点。

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define x first
#define y second
#define ll long long
#define PII pair<int, int>

// 核心思路就是先把攻击消耗完 看能产生多少爆炸

void solve()
{
    int n, m;
    cin >> n >> m;
    vector<int> a(n + 1), b(m + 1);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= m; i++)
        cin >> b[i];
    sort(a.begin() + 1, a.end()), sort(b.begin() + 1, b.end());
    int l = 1, r, bz = 0;
    // 先找到攻击次数
    int fg1 = 0;
    int cnt = 0;
    for (int i = 1; i <= n; i++)
    {
        if (a[i] == 1)
            fg1 = 1;
        else
            cnt++;
    }

    cnt += fg1;
    for (r = 1; r <= m; r++)
    {
        while (bz >= a[l] - 1 && l <= n) 
        // 这个地方需要减去1 我们寻求最大的爆炸方案 假设都攻击过了  看能不能使r到达m + 1就完了
        {
            l++;
            bz++;
        }
        if (bz >= b[r] && r <= m)
        {
            bz++;
            continue;
        }
        else
        {
            int nd = b[r] - bz;
            if (cnt >= nd)
            {
                cnt -= nd;
                bz++;
                continue;
            }
            else
            {
                cnt = 0;
                break;
            }
        }
    }
    if (r == m + 1)
    {
        cout << "Yes" << endl;
    }
    else
    {
        cout << "No" << endl;
    }
}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

G. GCD

知识点:暴力,找规律

分析:通过观察每次操作对双方造成的影响,我们会发现我们总能找到一种方案<26使a变成0,如果存在两个数都是偶数的情况,则一定可以消掉最后一位的两个0(二进制),如果存在一个数是奇数,则gcd一定是奇数,也可以消掉双方最后的一个1,所以我们发现,每两次操作一定可以让两个数的位数都-1,当a的位数为0,再进行一次操作就可以两个数都置0了,所以2^26的时间大小是6.4*10^7,这个时间复杂度是可以接受的,我们只需要去暴力枚举每次的操作是什么,找到最后的结果或>26退出即可,最后搜到的最小值就是我们要的答案。

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define x first
#define y second
#define ll long long
#define PII pair<int, int>

ll gcd(ll a, ll b)
{
    if (a == 0)
        return b;
    return gcd(b % a, a);
}
int mx;

void dfs(ll a, ll b, int cnt)
{
    if (cnt >= mx)
        return;
    if (a == 0)
    {
        mx = min(mx, cnt);
        return;
    }
    dfs(a - gcd(a, b), b, cnt + 1);
    dfs(a, b - gcd(a, b), cnt + 1);
    return;
}

void solve()
{
    int t;
    cin >> t;
    while (t--)
    {
        mx = 26;
        ll a, b;
        cin >> a >> b;
        // 枚举每次的操作
        dfs(a, b, 1);
        cout << mx << endl;
    }
}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    solve();
    return 0;
}

C. Coin

知识点:公式,找规律

分析:解决这个题目显然用逆向思考的思维是比较好的,到达最后终点需要多少轮是可以在根号n的时间内计算的,我们再反推这些轮数中最后一轮在位置1的最开始的位置在哪里。

我们需要通过计算知道两个结论:

        这一轮的人数为 n , 则下一轮人数会减少 n / k 上取整。

        这一轮的人数为 x , 则上一轮的人数为 x +  x / (k - 1) 上取整。

当 k <= √n的时候,当k >=√n的时候,我们发现总轮数不会超过k * log(n)。

每一轮,人数 n 的变化关系为 n_new = n_old - ceil(n_old / k),约等于 n_new ≈ n_old * (1 - 1/k)。 这是一个指数下降的过程。我们可以估算出总轮数 R 大约是 R ≈ k * ln(n)

现在我们来看 k <= √n 的情况: 当 k 取到最大值,即 k ≈ √n 时,总轮数 R ≈ √n * ln(n)ln(n) 是一个缓慢增长的函数,对于大数 n 来说,ln(n) 是大于 1 的。 所以 √n * ln(n) 明显大于 √n

对于 n=10^12√n=10^6ln(n)≈27.6。总计算量大约是 10^6 * 27.6,在 3 * 10^7范围,可以接受。

        while (n > 1)
        {
            n -= (n + k - 1) / k;
            sum++;
        }

当k>√n的时候我们发现每轮减少的人数,也就是 n / k的变换不会超过√n次,所以模拟计算变化的过程同时统计轮数即可。

倒推的时候思路是一样的,只要k都换成k-1即可。

#include <bits/stdc++.h>
using namespace std;
// #define endl "\n"
#define x first
#define y second
#define ll long long
#define PII pair<int, int>

void solve()
{
    ll n, k;
    cin >> n >> k;
    ll sum = 0;
    ll d = 1;
    if (k <= sqrt(n))
    { // 直接模拟即可找出轮数

        while (n > 1)
        {
            n -= (n + k - 1) / k;
            sum++;
        }
        // 知道了轮数就可以求出前面的结果是什么
        while (sum--)
        {
            d += (d + k - 2) / (k - 1);
        }
    }
    else
    {
        // n / k 的变化次数比较少
        while (n > 1)
        {
            ll bh = (n + k - 1) / k;
            ll nd = (n % k == 0 ? k : n % k);
            ll ls = (nd + bh - 1) / bh;
            sum += min(ls, (n - 1) / bh);
            n -= ls * bh;
        }
        while (sum > 0)
        {
            ll bh = (d + k - 2) / (k - 1);
            ll nd = (d % (k - 1) == 0 ? 1 : k - 1 - d % (k - 1));
            ll ls = (nd + bh - 1) / bh;
            d += min(ls, sum) * bh;
            sum -= ls;
        }
    }
    cout << d << endl;
}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    ll t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值