乐趣区

关于c++:2023-Hubei-Provincial-Collegiate-Programming-Contest题解

补题链接:https://codeforces.com/gym/104337

原文链接:https://www.eriktse.com/algorithm/1136.html

M. Different Billing

签到题,写几个柿子而后枚举 B 或 C 即可。

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

signed main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int x, y;cin >> x >> y;
    for(int c = 0;c <= x; ++ c)
    {int b = (y - 2500 * c) / 1000;
        if(b < 0 || 1000 * b + 2500 * c != y)continue;
        
        int a = x - b - c;
        
        if(a >= 0)
        {
            cout << a << '' << b <<' '<< c <<'\n';
            return 0;
        }
    }
    cout << -1 << '\n';
    return 0;
}

C. Darkness I

这题是对 Minecraft 中有限水的拓展过程为背景的一道思维题。

先考虑一下 n, m 均为奇数的状况:

而后从这种状况开始,减少一列,或者减少一行,都须要多加一个黑棋子,如果同时减少一行一列,那也是只须要减少一个棋子,减少到右下角的地位即可。

所以咱们依照这种构造方法输入答案即可。

#include <bits/stdc++.h>
#define int long long
using namespace std;
 
 
signed main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;cin >> n >> m;
    int res = (n + 1) / 2 + (m + 1) / 2 - 1;
    if(n % 2 == 0 || m % 2 == 0)res ++;
    cout << res << '\n';
    return 0;
}

J.Expansion

这题能够转化为以下题意:

一开始身上资源为 0,从 1 号点走到 n 号点,每个点上有一些资源(可能为正数),每秒钟能够抉择走或者不走,且每秒会失去以后点上的资源(此时的资源曾经做了前缀和),为了保障每时每刻身上的资源都不为正数,请问从 1 走到 n 所需的最小工夫。

咱们模仿一下这个过程,如果到了某个点发现如果在以后点停留 1 秒会使得资源变为正数,就阐明“我 应该在 右边的某个正数点多停留一会儿”,而为了使得停留时间起码,我会抉择 最大的点 进行停留。

留神一些特判,题意须要保障最初在 n 始终停留都不会使得资源为正数,所以 prefix[n] 须要大于等于零,还有为了使得能够走到n,须要保障第一个非 0 的数为负数。

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

const int N = 1e6 + 9, p = 998244353;

int a[N], prefix[N];

signed main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;cin >> n;
    for(int i = 1;i <= n; ++ i)cin >> a[i];
    for(int i = 1;i <= n; ++ i)prefix[i] = prefix[i - 1] + a[i];
    
    if(prefix[n] < 0)
    {
        cout << -1 << '\n';
        return 0;
    }
    // 遇到的第一个是正数
    for(int i = 1;i <= n; ++ i)
    {if(prefix[i] != 0)
        {if(prefix[i] < 0)
            {
                cout << -1 << '\n';
                return 0;
            }
            break;
        }    
    }
    
    int ans = 0, res = 0, mx = 0;
    // 后面一步步推动
    for(int i = 1;i <= n; ++ i)
    {mx = max(mx, prefix[i]);
        res += prefix[i];
        ans ++;// 走一秒
        if(res < 0)// 阐明走快了,应该在后面多停留一段时间的
        {
            // 补几秒钟
            int ti = (-res + mx - 1) / mx;
            ans += ti;
            res += ti * mx;
        }
    }
    cout << ans << '\n';
    return 0;
}

H.Binary Craziness

赛时卡这道题了,始终在想拆位的做法(造成惯性思维了 …… 蹩脚)。

其实这题咱们能够这样想,最多 m 条边,也就是所有点的出度之和肯定是 2m,而后咱们最多 n 个点,也就是说出度的品种数不会超过 $\sqrt{2m}$ 种。因为如果要使得品种数最多,那么就是每一种都只有一个,且从小到大,出度数组排序后(长度为 t)将会是1, 2, 3, 4, 5, ... 其和为 $\frac{t(t + 1)}{2} \le 2m$,所以长度 t 不会很大。

咱们就能够依据这个原理做一个桶记录一下某个数字呈现的次数,而后间接双重循环暴力写!

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

const int N = 1e6 + 9, p = 998244353;

int cnt[2 * N], a[N];

int f(int x, int y)
{return (x ^ y) * (x | y) * (x & y);
}

signed main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;cin >> n >> m;
    
    for(int i = 1;i <= m; ++ i)
    {
        int x, y;cin >> x >> y;
        a[x] ++, a[y] ++;
    }
    for(int i = 1;i <= n; ++ i)cnt[a[i]] ++;
    
    vector<int> v;
    for(int i = 1;i <= 2 * m; ++ i)if(cnt[i])v.push_back(i);
    int res = 0;
    for(int i = 0;i < v.size(); ++ i)
    {for(int j = i + 1;j < v.size(); ++ j)
        {res = (res + cnt[v[i]] * cnt[v[j]] % p * f(v[i], v[j]) % p) % p;
        }
    }
    cout << res << '\n';
    
    return 0;
}

F.Inverse Manacher

这题甚至不须要会马拉车。

题目给定一个“回文半径”的数组,要你还原还原出一种可能的字符串(仅蕴含 ab)。数据保障至多存在一种解。

只须要了解回文半径的含意即可。

当咱们走到 i 时,如果 a[i] > 1,阐明咱们要把 i 右边的一部分堆成到左边去,为了优化复杂度,咱们能够用双指针,r 示意此时 b 数组(也就是答案数组)的大小,也就是咱们更新到的右端,当 r < i + a[i] - 1 时,咱们就拓展失去 b[r],如果此时i > r,再依据一些状况来确定b[i] 即可(交替的取 a, b)。

留神数组开大一点,马拉车个别是两倍空间。

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

const int N = 3e6 + 9;

int a[N];

char b[N];

signed main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;cin >> n;
    for(int i = 0;i <= 2 * n + 1; ++ i)cin >> a[i];
    
    char ch = 'a';
    
    for(int i = 0, r = 0;i <= 2 * n + 1; ++ i)
    {while(i + a[i] - 1 > r)++ r, b[r] = b[2 * i - r];
        if(i == 0)b[i] = '&';
        else if(i & 1)b[i] = '|';
        else if(b[i] == 0)b[i] = ch, ch = (ch == 'a' ? 'b' : 'a');
    }
    for(int i = 2;i <= 2 * n + 1;i += 2)cout << b[i];
    return 0;
}

K.Dice Game

这题次要难在剖析出 n 个事件互相独立。

x 确定时,对于 n 集体当中的某一个人,他胜利的概率是 $p = \frac{m-x}{m-1}$,这个有两种了解,第一个理性的了解就是,当投出 y = x 是没有意义的,所以无效的 y 一共是 m 个,其中 m - x 个是能够赢的。

数学的了解是,这个人可能在第一次赢,也可能第二次赢,也可能第三次赢 …,设平局概率为t,胜利概率为q

$$ p= q + t * q + t^{2} * q + …. + t^{inf} * q $$

其中 $t=\frac{1}{m}$, $q = \frac{m-x}{m}$。

依据等比数列求和咱们能够晓得:

$$p = \frac{m-x}{m-1}$$

而后对于某个 x,一共有 n 集体,那么答案就是 $p^n = (\frac{m-x}{m-1}) ^ n$。

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

const int N = 1e6 + 9, p = 998244353;

int qmi(int a, int b)
{
    int res = 1;
    while(b)
    {if(b & 1)res = res * a % p;
        a = a * a % p, b >>= 1;
    }
    return res;
}

int inv(int x){return qmi(x, p - 2);}

signed main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;cin >> n >> m;
    int res = 1;
    for(int i = 1;i <= m; ++ i)
    {cout << qmi((m - i) * inv(m - 1) % p, n) << ' ';
    }
    
    return 0;
}

I.Step

已知 $2t | k(k+1)$,求最小的 $k$,其中 $t=lcm(p_1,p_2…,p_n)$。

无妨设 $2t=a*b$,且 $a|k,b|(k+1)$,且 $ax=k,by=(k+1)$,那么咱们能够失去:

$$ax+1=by$$

转换一下失去:

$$a(-x)+by=1$$

枚举 a,能够算出b,而后exgcd 搞出最小的 x,即失去了k=ax 答案。

当初问题是如何枚举 a,咱们看这个exgcd 式子能够发现咱们须要保障 gcd(a, b) = 1,且有2t = a * b,所以咱们能够对2t 进行惟一合成,而后选取不同质因子品种调配给 ab

调配计划能够通过二进制间接做。

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

const int N = 1e6 + 9, inf = 8e18;

int p[N];

int gcd(int a, int b){return b == 0 ? a : gcd(b, a % b);}
int lcm(int a, int b){return a / gcd(a, b) * b;}


map<int, int> mp;
vector<int> v;

void func(int x)
{for(int i = 2;i <= x / i; ++ i)
    {if(x % i)continue;
        v.push_back(i);
        mp[i] = 1;
        while(x % i == 0)mp[i] *= i, x /= i;
    }
    if(x > 1)v.push_back(x), mp[x] = x;
}

int exgcd(int a, int b, int &x, int &y)
{if(!b)return x = 1, y = 0, a;
    int d = exgcd(b, a % b, y , x);
    y -= a / b * x;
    return d;
}

signed main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;cin >> n;
    for(int i = 1;i <= n; ++ i)cin >> p[i];
    int lc = p[1];
    for(int i = 2;i <= n; ++ i)lc = lcm(lc, p[i]);
    
    // 对 2 * lc 进行质因数分解
    func(2 * lc);
    int res = inf;
    for(int i = 0;i < (1ll << v.size()); ++ i)
    {
        // 依据 i 失去 a
        int a = 1;
        for(int j = 0;j < v.size(); ++ j)
            if(i >> j & 1)a = a * mp[v[j]];

        int b = 2 * lc / a;
        int x, y, d = exgcd(a, b, x, y);
        x = -x;
        x = (x % (b / d) + (b /  d)) % (b / d);
        if(a * x)res = min(res, x * a);
        //cout << "a =" << a << '' <<"ax = "<< a * x <<'\n';
    }
    
    cout << res << '\n';
    
    return 0;
}
退出移动版