2023牛客寒假算法集训营4

A. 清楚姐姐学信息论(数学)

题意:

给定两个非负整数 x x x y y y ( 2 ≤ x , y ≤ 1 0 9 ) (2 \le x, y \le 10^9) (2x,y109) ,表示 x x x 进制和 y y y 进制,比较哪种进制的信息表示效率更高。

当且仅当, x x x 进制和 y y y 进制分别使用 x ⋅ y x \cdot y xy 个数时, x x x 进制能够表示的数字数目大于 y y y 进制,则称 x x x 进制的信息表示效率大于 y y y 进制。

思路一:

比较 x y x^y xy y x y^x yx​ 的大小就行。

但是你这样写会发现,炸了,数据太大了,所以需要优化一下:

改成比较 y ⋅ l o g x y·log_x ylogx x ⋅ l o g y x·log_y xlogy​ 就行了。

特别注意:优化后要用浮点数去比较,存在精度问题,如果用整数比较会有部分测试点过不了。

代码:

#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;

int main()
{
    ll x, y;
    cin >> x >> y;

    if (x == y) cout << x << endl;
    else {
        double xx = y * log(x);
        double yy = x * log(y);
        if (xx > yy) cout << x << endl;
        else if (xx < yy) cout << y << endl;
        else cout << min(x, y) << endl;
    }

    return 0;
}

思路二:

思路二就比较神奇,用的是数学的方法。

结论: e e e 进制是效率最高的进制。

因此越靠近 e e e 进制效率越高, e ≈ 2.7 e ≈ 2.7 e2.7 ,所以在比较 ( 2 , 3 ) (2, 3) (2,3) 时, 3 3 3 进制效率更高,其他情况都是小的进制更优。

代码:

#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;

int main()
{
    ll x, y;
    cin >> x >> y;

    if (x == 2 || y == 3) cout << 3 << endl;
    else cout << min(x, y) << endl;

    return 0;
}

B. 清楚姐姐学构造(数学 + 构造)

题意:

给定一个长度为 N N N 的数组 c c c 和一个质数 m m m,请你构造另外两个 同余等式 数组 a , b a,b a,b 满足:

{ a i ≡ a N − 1 − i ( m o d m ) b i ≡ − b N − 1 − i ( m o d m ) c i ≡ a i + b i ( m o d m ) \left\{\begin{matrix} a_{i}\equiv a_{N-1-i}\pmod{m} \\ b_{i}\equiv -b_{N-1-i}\pmod{m} \\ c_{i}\equiv a_{i}+b_{i}\pmod{m} \end{matrix}\right. aiaN1i(modm)bibN1i(modm)ciai+bi(modm)

数组 a , b , c a,b,c a,b,c 的下标均从 0 0 0 开始计算。

如果可以构造出数组 a , b a,b a,b,则首先输出 Y e s Yes Yes,然后输出任意一种解,否则只需输出一行一个字符串 N o No No

≡ \equiv 为同余符号,它表示两个整数对 m m m 取余数的结果相同,对于负数取余数,若结果仍为负数,则需要加上 m m m

思路:

一个重要结论:任何一个函数都可以表示成奇函数和偶函数的和

a a a 数组就是两边的数关于中心对称轴相等,而 b b b​​ 数组则是关于中心对称轴相反。

要求 c i = a i + b i c_i = a_i + b_i ci=ai+bi​ ,即要求构造一个奇函数和一个偶函数来表示任意一个函数。

出题人题解

任取一个数列 c c c,设 c [ i ] ≡ a [ i ] + b [ i ] c[i]\equiv a[i]+b[i] c[i]a[i]+b[i] ,设 a [ i ] a[i] a[i] 数列关于 N / 2 N/2 N/2 对称, b [ i ] b[i] b[i] 数列关于 N / 2 N/2 N/2 对称相反。

c [ N − i − 1 ] ≡ a [ N − i − 1 ] + b [ N − i − 1 ] c[N-i-1]\equiv a[N-i-1]+b[N-i-1] c[Ni1]a[Ni1]+b[Ni1] .

两式相加可得 c [ i ] + c [ N − i − 1 ] ≡ a [ i ] + b [ i ] + a [ N − i − 1 ] + b [ N − i − 1 ] c[i]+c[N-i-1] \equiv a[i]+b[i]+ a[N-i-1]+b[N-i-1] c[i]+c[Ni1]a[i]+b[i]+a[Ni1]+b[Ni1] ,根据题意, b [ i ] b[i] b[i] 数列对称相反消掉, a [ i ] a[i] a[i] 数列关于 N / 2 N/2 N/2 对称则 a [ i ] ≡ a [ N − i − 1 ] a[i] \equiv a[N-i-1] a[i]a[Ni1] 。化简得

c [ i ] + c [ N − i − 1 ] ≡ 2 ⋅ a [ i ] c[i]+c[N-i-1] \equiv 2\cdot a[i] c[i]+c[Ni1]2a[i] a [ i ] ≡ ( c [ i ] + c [ N − i − 1 ] ) ⋅ i n v ( 2 ) a[i] \equiv (c[i]+c[N-i-1])\cdot inv(2) a[i](c[i]+c[Ni1])inv(2) 。注意当 m = 2 m=2 m=2 无逆元需要特殊处理。

相似的,两式相减得 b [ i ] ≡ ( c [ i ] − c [ N − i − 1 ] ) ⋅ i n v ( 2 ) b[i] \equiv (c[i]-c[N-i-1])\cdot inv(2) b[i](c[i]c[Ni1])inv(2) 。和之前一样, m = 2 m=2 m=2 时无逆特殊处理即可。

代码:

#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
const int N = 1e5 + 10;

ll n, m;
ll a[N], b[N], c[N];

ll qmi(ll a, ll b)  //快速幂求逆元
{
    ll res = 1;
    a = (a % m + m) % m;
    while (b)
    {
        if (b & 1) res = res * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return res;
}

bool check()
{
    for (int i = 1; i <= n; i++)
        if (c[i] != c[n - i + 1])
            return false;
    return true;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    cin >> n >> m;

    for (int i = 1; i <= n; i++){
        cin >> c[i];
    }

    if (m == 2){
        if (check()){
            cout << "Yes" << endl;
            
            for (int i = 1; i <= n; i++)
                cout << c[i] << ' ';
            cout << endl;

            for (int i = 1; i <= n; i++)
                cout << 0 << ' ';
            cout << endl;
        }
        else cout << "No" << endl;
    }
    else {
        ll inv2 = qmi(2, m - 2);  //对2特判处理

        for (int i = 1; i <= n; i++){
            a[i] = (c[i] + c[n - i + 1]) * inv2 % m;
            b[i] = (c[i] + m - c[n - i + 1]) * inv2 % m;
        }

        cout << "Yes" << endl;
        for (int i = 1; i <= n; i++)
            cout << a[i] << ' ';
        cout << endl;

        for (int i = 1; i <= n; i++)
            cout << b[i] << ' ';
        cout << endl;
    }

    return 0;
}

C. 清楚姐姐学01背包(Easy Version)(01背包)

题意:

n n n 个物品,每个物品的体积为 w w w ,价值为 v v v ,背包的容量为 m m m

可以从 n n n​ 个物品中任选若干个放入背包,要保证总体积不能超过背包容量。

现定义背包中所放物品价值总和的最大值为 V V V ,从这 n n n 个物品中去掉某一个物品后,从剩余 n − 1 n - 1 n1 个物品中任选若干个放入背包,后来所放物品价值总和的最大值为 V ′ V' V

V ′ < V V' < V V<V​ ,则称这个物品为必选物品。

求出对于每一个物品,它的价值再加上多少,能称为一个必选物品。

思路:

比普通的 01 背包题多了一个条件。

枚举每个物品,除了这个物品外,计算其他物品的01背包,再判断该物品是否必须取,它还需加上的价值就是 V - V'

代码:

#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
const int N = 110;

int n, m;
ll w[N], v[N];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    cin >> n >> m;

    for (int i = 1; i <= n; i++){
        cin >> w[i] >> v[i];
    }

    for (int i = 1; i <= n; i++){
        vector<ll> dp(m + 1);
        for (int j = 1; j <= n; j++){
            if (i != j){
                for (int k = m; k >= w[j]; k--){
                    dp[k] = max(dp[k], dp[k - w[j]] + v[j]);
                }
            }
        }

        ll res = dp[m] - (dp[m - w[i]] + v[i]) + 1;
        if (res < 0) cout << 0 << endl;
        else cout << res << endl;
    }

    return 0;
}

D. 清楚姐姐学01背包(Hard Version)(01背包)

题意:

与 C 题一样,不同的是数据范围变大。 ( 1 ≤ n , m ≤ 5000 ) (1 \le n, m \le 5000) (1n,m5000)

思路:

歪门邪道法:

类似于上题,既然琺暴力,那就分组求背包,以 75 个为一组,分成 75 个背包,第 i i i 个背包中不含第 i i i 组的物品。

其中对于第 j j j 个物品,找到其所在组数,把这组除了 j j j 以外的物品装入背包,再使用 C 题的方法判断即可。

再放上正经的解法:出题人题解

代码:

#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
const int N = 5010;

int n, m;
ll w[N], v[N];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    cin >> n >> m;

    vector dp(76, vector<ll>(m + 1));
    for (int i = 1; i <= n; i++){
        cin >> w[i] >> v[i];
        int t = i / 75;
        for (int k = 0; k < 75; k++){
            if (k != t){
                for (int j = m; j >= w[i]; j--){
                    dp[k][j] = max(dp[k][j], dp[k][j - w[i]] + v[i]);
                }
            }
        }
    }

    for (int i = 1; i <= n; i++){
        int t = i / 75;
        auto f = dp[t];
        for (int j = 1; j <= n; j++){
            if (i != j && t == j / 75){
                for (int k = m; k >= w[j]; k--){
                    f[k] = max(f[k], f[k - w[j]] + v[j]);
                }
            }
        }

        ll ans = f[m] - f[m - w[i]] - v[i] + 1;
        
        if (ans < 0) cout << 0 << endl;
        else cout << ans << endl;
    }

    return 0;
}

E. 清楚姐姐打怪升级(数学 or 二分)

题意:

打怪游戏,有 n n n 只怪物,每只怪物的生命上限为 h h h ,生命每时刻恢复 v v v 点。

每次攻击的间隔为 t t t ,攻击力为 a a a

在每个时刻初,若怪物的生命值不满,则恢复 v v v 点生命值,但是不能超过生命值上限 h h h

每次攻击可以选择一只怪物造成 a a a 点伤害,若此时怪物生命值小于等于 0 0 0 ,则怪物死亡。

现在问至少在第几个时刻末可以杀死所有怪物,若永远无法杀死所有怪物则输出 − 1 -1 1

思路:

已知 n n n 只怪物的血量,回血速度,自身攻击力和攻击间隔,求打死所有怪物的时间。(注意,攻击是从时刻 1 1 1 开始的)

如果不能一刀解决,那么该怪物必定会经过 掉血 + 回血 的循环。

因此先把最后一刀减去,然后计算剩下的血量何时能扣完。

假设砍一刀后血量为 w = a − v w = a - v w=av ,如果 w ≤ 0 w \le 0 w0 则无解,否则计算总轮数 s = ⌈ h w ⌉ + 1 s = \lceil \frac{h}{w}\rceil + 1 s=wh+1 ,时间为 ( s − 1 ) ⋅ t + 1 (s - 1)·t + 1 (s1)t+1

也可以二分计算:

二分恰好 x x x 次打死怪物,那么 check 时,前面已经攻击了 x − 1 x - 1 x1 次,加上血量回复,再补上第 x x x 次攻击,若怪物血量 h ≤ 0 h \le 0 h0 ,则 check = true

代码:

#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
const int N = 1e5 + 10;

ll n, t, a;
ll h[N], v[N];

bool check()
{
    for (int i = 1; i <= n; i++){
        if (a <= v[i] && a < h[i])
            return false;
    }

    return true;
}

int main()
{
    cin >> n >> t >> a;

    for (int i = 1; i <= n; i++){
        cin >> h[i] >> v[i];
        v[i] *= t;
    }

    if (!check()){
        cout << -1 << endl;
    }
    else{
        ll res = 0;
        for (int i = 1; i <= n; i++){
            if (h[i] <= a) res++;
            else {
                ll w = a - v[i];
                res += (h[i] - a + w - 1) / w + 1;  //向上取整
            }
        }

        cout << (res - 1) * t + 1 << endl;
    }

    return 0;
}

F. 清楚姐姐学树状数组(二叉树 + DFS)

题意:

有一个尺寸大小为 N = 2 k N=2^{k} N=2k 的树状数组,按照如下的规则构造出一个"树状数组二叉树"。

  1. 编号为 i i i 的节点的深度为 l o g 2 ( l o w b i t ( N ) ) − l o g 2 ( l o w b i t ( i ) ) log2(lowbit(N))-log2(lowbit(i)) log2(lowbit(N))log2(lowbit(i))

  2. 整棵二叉树的中序遍历节点编号顺序为 1 , 2 , 3... N − 1 , N 1,2,3...N-1,N 1,2,3...N1,N

问对于树状数组生成的二叉树上编号为 x x x 的节点,分别在前序、中序、后序遍历中是第几个被遍历到的节点?

思路:

给一个树状数组,建成一个二叉树,查询若干个节点的前中后序遍历。

树状数组中的两条重要路径为 x → x + l o b i t ( x ) x→x+lobit(x) xx+lobit(x) x → x − l o b i t ( x ) x→x-lobit(x) xxlobit(x)

  • 当一个节点是左孩子时,其父节点是 x → x + l o b i t ( x ) x→x+lobit(x) xx+lobit(x)
  • 当一个节点是右孩子时,其父节点是 x → x − l o b i t ( x ) x→x-lobit(x) xxlobit(x)

因为长度大小为 2 k 2^k 2k 的树状数组除根节点外都是全满的,大小实际上就是 2 m − 1 2^m − 1 2m1 可以直接计算,所以 DFS 模拟前序和后序的过程中跳过整颗的子树部分直接计算答案即可。

代码:

#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
const int N = 2010;

ll k, q;
ll n;

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

ll size(ll x)
{
    return (lowbit(x) << 1) - 1;
}

bool is_lchild(ll x)
{
    return !(x & (lowbit(x) << 1));
}

ll fa(ll x)
{
    if (is_lchild(x))
        return x + lowbit(x);
    return x - lowbit(x);
}

ll lch(ll x)
{
    return x ^ lowbit(x) ^ (lowbit(x) >> 1);
}

ll rch(ll x)
{
    return x ^ (lowbit(x) >> 1);
}

ll L(ll x)
{
    ll root = n, ret = 1;
    while (root != x)
    {
        ret++;
        if (x < root){
            root = lch(root);
        }
        else {
            ret += size(lch(root));
            root = rch(root);
        }
    }
    return ret;
}

ll R(ll x)
{
    if (x == n) return n;

    ll root = x, ret = size(root);
    while (root != n)
    {
        if (root == rch(fa(root))){
            ret += size(lch(fa(root)));
        }
        root = fa(root);
    }
    return ret;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    cin >> k >> q;
    n = 1ll << k;
    while (q--){
        ll x;
        cin >> x;
        cout << L(x) << ' ' << x << ' ' << R(x) << endl;
    }

    return 0;
}

G. 清楚姐姐逛街(Easy Version)(BFS)

题意:

A,B 两人走地图,要求 B 在最短的时间内追上 A 。

地图可以视为是一个 N ⋅ M N \cdot M NM 的二维平面,左上角为坐标原点 ( 0 , 0 ) (0,0) (0,0) ,向下为 x x x 轴正方向,向右为 y y y 轴正方向,入口位于点 s ( x s , y s ) s(x_s,y_s) s(xs,ys)

地图行走规则如下:

‘L’ – 向左,‘R’ – 向右,‘U’ – 向上,‘D’ – 向下

‘.’ – 表示终止位置,当移动到这里后将永远停在该位置

‘#’ – 表示墙壁,任何人都不能移动到这里

B 追 A,且 B 可以随意移动或停在原地,不必遵守地图的规则。

B 从入口处出发,有 Q Q Q 次询问,每次输入一个坐标表示 A 从该位置开始移动,问至少要多久才能追上 A ,若无论如何都追不上则输出 -1.

思路:

首先预处理 B 到达每个位置所花费的时间,因为 B 可以随便走,所以先用 BFS 遍历连通块,计算到达每个位置的时间。

然后对于每次询问,A 按照地图规则来走,直到 A到达某个位置的时间 ≥ B到达的时间,则 B 能追上 A 。

代码:

#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
#define PII pair<int, int>
using namespace std;
const int N = 810;

int n, m, sx, sy, Q;
char mp[N][N];
int d[N][N];

void bfs()
{
    queue<PII> q;
    q.push({sx, sy});
    d[sx][sy] = 0;

    while (!q.empty())
    {
        int x = q.front().first, y = q.front().second;
        q.pop();
        int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
        for (int i = 0; i < 4; i++){
            int xx = x + dx[i], yy = y + dy[i];
            if (xx >= 1 && xx <= n && yy >= 1 && yy <= m 
             && d[x][y] + 1 < d[xx][yy] && mp[xx][yy] != '#'){
                d[xx][yy] = d[x][y] + 1;
                q.push({xx, yy});
            }
        }
    }
}

int main()
{
    memset(d, 63, sizeof d);

    cin >> n >> m >> sx >> sy >> Q;
    sx++, sy++;

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> mp[i][j];
        
    bfs();

    while (Q--){
        int x, y;
        cin >> x >> y;
        x++, y++;

        int f = 1, res = 1;
        while (1)
        {
            int lax = x, lay = y;
            if (mp[x][y] == 'U' && mp[x - 1][y] != '#') x--;
            else if (mp[x][y] == 'D' && mp[x + 1][y] != '#') x++;
            else if (mp[x][y] == 'R' && mp[x][y + 1] != '#') y++;
            else if (mp[x][y] == 'L' && mp[x][y - 1] != '#') y--;

            if (lax == x && lay == y){
                if (d[x][y] >= 1e9){
                    f = 0;
                    break;
                }
            }

            if (d[x][y] <= res){
                res = max(d[x][y], res);
                break;
            }
            res++;
        }

        if (!f) cout << -1 << endl;
        else cout << res << endl;
    }

    return 0;
}

L. 清楚姐姐的三角形I(数学 + 推公式)

题意:

假设三角形的三条边的长度分别为 a , b , c a,b,c a,b,c

定义三角形的三个顶点分别为 A , B , C A,B,C A,B,C,顶点 A A A 连接 b , c b,c b,c 两边;顶点 B B B 连接 a , c a,c a,c 两边;顶点 C C C 连接 a , b a,b a,b 两边。

定义三角形三个顶点的权值 V A = l b + l c , V B = l a + l c , V C = l a + l b V_A=l_b+l_c,V_B=l_a+l_c,V_C=l_a+l_b VA=lb+lc,VB=la+lc,VC=la+lb

现在给定 V A , V B , V C V_A,V_B,V_C VA,VB,VC ​的值,求 a , b , c a,b,c a,b,c ​的值。

思路:

我们根据三角形已知条件推出三条边的表达式:

a = ( v b + v c − v a ) / 2 a = (v_b + v_c - v_a) / 2 a=(vb+vcva)/2
b = ( v a + v c − v b ) / 2 b= (v_a + v_c - v_b) / 2 b=(va+vcvb)/2
c = ( v a + v b − v c ) / 2 c = (v_a + v_b - v_c) / 2 c=(va+vbvc)/2

然后根据组成三角形的条件:任意两边之和大于第三边,任意两边之差小于第三边

判断这三条边能否组成三角形即可。

代码:

#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
const int N = 2e5 + 10;

void solve()
{
    ll va, vb, vc;
    cin >> va >> vb >> vc;

    ll s = (va + vb + vc) / 2;
    if (s < 3){
        cout << "NO" << endl;
    }
    else {
        ll a = (vb + vc - va) / 2;
        ll b = (va + vc - vb) / 2;
        ll c = (va + vb - vc) / 2;

        if (a + b > c && b + c > a && c + a > b
           && abs(a - b) < c && abs(b - c) < a && abs(c - a) < b
           && va == b + c && vb == a + c && vc == a + b){
            cout << "YES" << endl;
            cout << a << ' ' << b << ' ' << c << endl;
        }
        else {
            cout << "NO" << endl;
        }
    }
}

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

    return 0;
}

M. 清楚姐姐的三角形II(构造)

题意:

要求构造一个大小为 n n n 的数组,满足数组中每相邻的三项均不能组成三角形,输出这个数组。

思路:

你以为是斐波那契???NoNoNo,请下载国家反诈APP.

题目只要求相邻三项无法构成三角形,条件很少,怎么样构造都可以。

我们任意选三个不能组成三角形的数循环就行了,这里用的是 1 1 2

代码:

#include <bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
const int N = 1e5 + 10;

int a[N];

int main()
{
    int n;
    cin >> n;

    for (int i = 1; i <= n; i++){
        if (i % 3) cout << 1 << ' ';
        else cout << 2 << ' ';
    }
    cout << endl;

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BraumAce

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值