2022牛客寒假集训营1

题目链接:link

L.牛牛学走路

签到题1

J.小朋友做游戏

签到题2

E.炸鸡块君的高中回忆

题目

\(n\) 个人只带了 \(m\) 张校园卡,于是他们想到先让 \(m\) 个人进学校再派一个人带着 \(m\) 张卡出来,重复上述过程直到所有人都进入学校

假设进校和出校都要花费一个单位时间,求所有人都进校花费的最少时间,若无法让所有人都进校,输出 \(-1\)

分析

首先判断无法让所有人都进校的情况: \(n\not=1\text{ and } m=1\)

注意到每次 \(m\) 人进校 \(1\) 人出校减少了 \(m-1\) 个人,花费了 \(2\) 个单位时间,当剩余的人是 \(m\)\(m-1\) 时可以直接一起进校,不用再派人出校,只花费了 \(1\) 个单位时间

所以 \(n\bmod(m-1)\leq 1\) 时循环了 \(\lfloor\frac{n}{m-1}\rfloor\) 次,最后一次只花费了 \(1\) 单位时间,答案为 \(2\lfloor\frac{n}{m-1}\rfloor-1\)

否则循环了 \(\lfloor\frac{n}{m-1}\rfloor+1\) 次,答案为 \(2\lfloor\frac{n}{m-1}\rfloor+1\)

\(m=2\) 时这个公式不适用,答案为 \(2n-3\)

总结

本题是这次比赛AC的六道题中WA次数最多的,过了很久才发现 \(m=2\) 时的特殊情况,做简单题时应该注意分类的完整性

代码
#include<bits/stdc++.h>
using namespace std;

int main()
{
    int t;
    scanf("%d", &t);
    while(t--) {
        int n, m;
        scanf("%d%d", &n, &m);
        if(n == m) {
            printf("1\n");
        } else if(m == 1) {
            printf("-1\n");
        } else if(m == 2) {
            printf("%d\n", 2 * n - 3);
        }else {
            if(n % (m - 1) <= 1)
                printf("%d\n", 2 * (n / (m - 1)) - 1);
            else
                printf("%d\n", 2 * (n / (m - 1)) + 1);
        }
    }
    return 0;
}

H.牛牛看云

题目

给定数列 \(\{a_n\}\) ,求 \(\sum_{i=1}^n\sum_{j=i}^n|a_i+a_j-1000|\)

分析

暴力求解显然会超时,所以需要做一些变通,设 \(b_i=a_i-500\) ,那么题目就变成了 \(\sum_{i=1}^n\sum_{j=i}^n|b_i+b_j|\)

由于和式的相加顺序可以任意变换,所以我们可以对 \(\{b_n\}\) 进行排序,求和的表达式仍不变,并维护 \(\{b_n\}\) 的前缀和

对于每个 \(b_i\) ,若 \(\exists r \text{ s.t. } b_i+b_r < 0 \text{ and } b_i+b_{r+1} \geq 0\) ,那么 \(b_i+b_{r+1},b_i+b_{r+2},\cdots,b_i+b_n\) 都大于 \(0\) ,可以去绝对值,同理,\(b_i+b_{i+1},b_i+b_{i+2},\cdots,b_i+b_{r}\) 都小于 \(0\) ,也能去绝对值

去掉绝对值后,就可以用前缀和进行计算:

\[\sum_{j=i}^n |b_i+b_j|=sum(r+1,n)-sum(i+1,r)+2|b_i|+(n-2r+i)b_i \]

\(\not\exist r\) 时,我们设 \(r=i\) ,也可以用上式计算

总结

本题由于没有开 long long WA了一次,一开始以为不会超出范围,当对是否溢出不确定时应当尽量开 long long

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

const int MAX_N = 1000000 + 5;
int n;
ll b[MAX_N];
ll sum[MAX_N];

ll s(int l, int r)
{
    if(l > r)
        return 0;
    return sum[r] - sum[l - 1];
}

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%lld", &b[i]);
        b[i] -= 500;
    }
    sort(b + 1, b + n + 1);
    for(int i = 1; i <= n; i++)
        sum[i] = sum[i - 1] + b[i];
    int r = n;
    ll ans = 0;
    for(int i = 1; i <= n; i++) {
        while(b[i] + b[r] > 0 && r > i)
            r--;
        ans = ans + s(r + 1, n) - s(i + 1, r) + (n - 2 * r + i) * b[i];
        ans = ans + 2ll * abs(b[i]);
        if(r == i)
            r++;
    }
    printf("%lld\n", ans);
    return 0;
}

A.九小时九个人九扇门

题目

\(n\) 个人被困在房间里,房间有 \(9\) 扇门,他们要打开这 \(9\) 扇门

每个人的腕表上都有一个数字, \(k\) 个人能够打开门上数字为 \(d\) 的数字门当且仅当这 \(k\) 个人的腕表数字之和的数字根恰好为 \(d\)

一个数的数字根是指:将该数字各数位上的数字相加得到一个新的数,如果这个新数大于 \(10\) 就重复这一过程,最后得到的小于 \(10\) 的数即为数字根

现在,你知道这 \(n\) 个人的腕表数字,分别计算可以打开 \(1-9\) 号门的人物组合有多少种

分析

考虑如何快速计算数字根,一个数的数字根即为它对 \(9\) 取模得到的值

证明:设一个数 \(a\)\(\overline{a_na_{n-1}\cdots a_1}\) ,则 \(a=10^{n-1}a_n+10^{n-2}a_{n-1}+\cdots+a_1\) ,设它的数字根为 \(f(a)\) ,由定义可得:\(f(a)=a_1+a_2+\cdots+a_n\) ,只需证明每次迭代后对 \(9\) 取模得到的值不变,即证明:

\[\overline{a_na_{n-1}\cdots a_1}\equiv a_1+a_2+\cdots+a_n\pmod{9} \]

移项可得:

\[(10^{n-1}-1)a_n+(10^{n-2}-1)a_{n-1}+\cdots+9a_2\equiv 0\pmod{9} \]

显然正确,得证

\(f[i][j]\) 为前 \(i\) 个人里打开 \(j\) 号门的人物组合种数,可以得到类似0/1背包的转移方程

总结

比赛时误以为本题很难,后来看到AC率后才试了试,对于与数字相关的题目,有时关键在于推出一个简化计算的结论

代码
#include<bits/stdc++.h>
using namespace std;

const int MOD = 998244353;
const int MAX_N = 100000 + 5;
int a[MAX_N], dp[9][MAX_N];

int main()
{
    int n, t;
    scanf("%d", &n);
    dp[0][0]=1;
    for (int i = 1; i <= n; i++) {
	scanf("%d", &a[i]);
	for (int j = 0; j < 9; j++) {
	    t = (j + a[i]) % 9;
	    dp[t][i] = (dp[t][i - 1] + dp[j][i - 1]) % MOD;
	}
    }
    for (int i = 1; i < 9; i++)
        printf("%d ", dp[i][n]);
    printf("%d\n", dp[0][n] - 1);
}

D.牛牛做数论

题目

定义 \(H(x)=\frac{\varphi(x)}{x},x \geq 2\) ,对于一个整数 \(n\) 你需要求出使 \(H(x)\)\([2,n]\) 上得到最小值和最大值的 \(x_1,x_2\)

分析

根据欧拉函数的表达式我们可以进行化简:

\[H(x)=\prod_{i=1}^n(1-\frac{1}{p_i}),x=p_1^{c_1}p_2^{c_2}\cdots p_n^{c_n} \]

所以当 \(x_1\) 包含的素因子尽量多时取到最小值,因为每一个素因子都可以使表达式多一个小于 \(1\) 的乘项,此时 \(c_1=c_2=\cdots=c_n=1\)

再考虑最大值,如果 \(x_2\) 为合数,由于 \(H(x)\) 为积性函数,那么 \(x_2=p_1^{c_1}p_2^{c_2}\cdots p_n^{c_n}\)\(H(x_2)=H(p_1^{c_1})H(p_2^{c_2})\cdots H(p_n^{c_n})\leq H(p_1^{c_1})\) 与最大值矛盾,所以 \(x_2\) 为素数,而定义域为素数时 \(H(x)=\frac{x-1}{x}\) 单调递增,所以 \(x_2\) 为小于等于 \(n\) 的最大素数

总结

以上证明在比赛时没有细想,在考后才梳理了一遍,本题也因为没开 long long WA了一次

代码
#include<bits/stdc++.h>
using namespace std;

long long prime[1005];
int cnt = 0;

bool pd(int num)
{
    if(num < 2)
        return false;
    for(int i = 2; i * i <= num; i++)
        if(num % i == 0)
            return false;
    return true;
}

int main()
{
    for(int i = 2; i <= 1000; i++) {
        if(pd(i)) {
            cnt++;
            prime[cnt] = i;
        }
    }
    int t;
    scanf("%d", &t);
    while(t--) {
        int n;
        long long ans1 = 1, ans2;
        scanf("%d", &n);
        if(n == 1) {
            printf("-1\n");
            continue;
        }
        int i;
        for(i = 1; ans1 < n; i++)
            ans1 *= prime[i];
        if(ans1 > n)
            ans1 /= prime[i - 1];
        for(i = n; i >= 2; i--) {
            if(pd(i)) {
                ans2 = i;
                break;
            }
        }
        printf("%lld %lld\n", ans1, ans2);
    }
    return 0;
}

I.B站各唱各的

题目

\(n\) 个人唱一首共有 \(m\) 句的歌曲,每两个人之间不能互相交流

对于这首歌中的每一句,都可以选择唱或不唱

若某句歌词所有人都唱了或所有人都没唱,则认为这句唱失败了,否则认为这句唱成功了

\(n\) 个人想选择一个最佳的策略使成功唱出的句子尽量多,求期望唱成功的句子数量,并对 \(10^9+7\) 取模

分析

设每个人唱第 \(i\) 句歌词的概率都为 \(p_i\) ,则唱失败的概率为:

\[\prod_{i=1}^n p_i+\prod_{i=1}^n (1-p_i)\geq (\frac{1}{2})^{n-1} \]

当且仅当 \(p_1=p_2=\cdots=p_n=\frac{1}{2}\) 时取等

唱失败的句子数量期望则为:

\[m\times(\prod_{i=1}^n p_i+\prod_{i=1}^n (1-p_i))\geq m(\frac{1}{2})^{n-1} \]

那么唱成功的数量的最大期望为:

\[\frac{m(2^n-2)}{2^n} \]

由于要取模,所以需要求逆元,而模数为素数,只要用费马小定理+快速幂即可求出

总结

比赛时认为本题很难,看了看题就没想了

代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
  
const int MOD = 1e9 + 7;
  
ll qpow(ll a, ll b)
{
    a %= MOD;
    ll ans = 1;
    for(; b; b >>= 1) {
        if (b & 1)
            ans = ans * a % MOD;
        a = a * a % MOD;
    }
    return ans;
}
 
int main()
{
    int t;
    scanf("%d", &t);
    while (t--) {
        ll n, m;
        cin >> n >> m;
        ll num = qpow(2, n) % MOD;
        ll x = (num - 2 + MOD) % MOD * qpow(num, MOD - 2) % MOD * m % MOD;
        printf("%lld\n", x);
    }
}

F.中位数切分

题目

给定一个项数为 \(n\) 的数列和一个整数 \(m\) ,你需要将其分割成若干个子数列使每个子数列的中位数都大于等于 \(m\) ,求最多可以划分成多少子数列,如果无论如何分段都无法满足限制则输出 \(-1\) ,否则输出可行的最大分段数

定义偶数个数的数列中位数为排序后中间两个数中较小的那个,奇数个数的数列中位数就是排序后正中间的数

分析

\(cnt_1(l,r),cnt_2(l,r)\) 分别为 \(a_l,a_{l+1},\cdots,a_r\) 中大于等于 \(m\) 的数字个数,和小于 \(m\) 的数字个数

\(f(l,r)=cnt_1(l,r)-cnt_2(l,r)\)

\(f\) 有如下性质:

  • \(f(l,r)>0\) 则该段满足中位数大于等于 \(m\)

    证明:\(f(l,r)>0 \Rightarrow cnt_1(l,r)\geq cnt_2(l,r)+1\) 所以无论长度奇偶,中位数都一定在 \(cnt_1\) 代表的数中取

  • \(f(l,r)=f(l,m)+f(m+1,r)\)

    证明:容易证明 \(cnt(l,r)=cnt(l,m)+cnt(m+1,r)\) 再代入即得证

\(f(1,n)\leq 0\) 无论如何分段都无法满足限制

证明:如果在满足限制的情况下将数列分成 \(k\) 段,每段长度分别为 \(b_1,b_2,\cdots,b_k\) ,每段大于等于 \(m\) 的数字个数为 \(c_1,c_2,\cdots,c_k\) ,那么如果满足限制,当 \(b_i\) 为偶数时, \(c_i\geq\frac{b_i}{2}+1\) ,当 \(b_i\) 为奇数时, \(c_i\geq\frac{b_i+1}{2}\) ,进一步有:

\[cnt_1(1,n)=c_1+c_2+\cdots+c_k\geq \frac{b_1+b_2+\cdots+b_k+k}{2}=\frac{n+k}{2} \]

所以 \(cnt_2(1,n)=n-cnt_1(1,n)<cnt_1(1,n)\Rightarrow f(1,n)>0\)

现在证明:\(f(1,n)>0\)\(f(1,n)\) 即为答案

在区间 \([l,r]\) 内如果可以找到一个位置 \(m\) 使 \(f(l,m)>0\text{ and }f(m+1, r) >0\) 则在 \(m\) 处将数列切开后两个子数列依然满足条件,所以我们需要找出 \(m\) 存在的条件,当 \(f(l,r) > 1\) 时,由于对于任意 \(x\)\(|f(l,x)-f(l,x+1)|=1\)\(f(l,l-1)=0\) ,所以类似于中值定理的形式,必然存在 \(m\) 使 \(f(l,m)=1\) ,此时 \(f(m+1,r)=f(l,r)-f(l,m)>1-1=0\) ,所以 \(f(l,r)>1\) 时可以切割

综上,由于要尽可能分成更多段,所以对于结果的每一段 \([l_i,r_i]\) 都有 \(f(l_i,r_i)=1\) ,否则可以继续分段,那么答案即为:

\[\sum_{i=1}^k 1=\sum_{i=1}^k f(l_i,r_i)=f(1,n) \]

总结

比赛时的思路是用线段树之类的数据结构维护中位数,然后再dp,没想到简单做法的证明如此复杂,不知为何本题提交数和AC率如此高

代码
#include<bits/stdc++.h>
using namespace std;

int main()
{
    int t;
    scanf("%d", &t);
    while(t--) {
        int n, m, a, cnt1 = 0, cnt2 = 0;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a);
            if(a >= m)
                cnt1++;
            else
                cnt2++;
        }
        if(cnt1 - cnt2 <= 0)
            printf("-1\n");
        else
            printf("%d\n", cnt1 - cnt2);
    }
    return 0;
}

B.炸鸡块君与FIFA22

题目

在排位系统中,每局游戏可能有胜利(用W表示)、失败(用L表示)、平局(用D表示)三种结果,胜利将使得排位分加一、失败使排位分减一、平局使排位分不变。特别地,该排位系统有着存档点机制,其可以简化的描述为:若你当前的排位分是 \(3\) 的整倍数(包括 \(0\) 倍),则若下一局游戏失败,你的排位分将不变(而不是减一)

现在,给定一个游戏结果字符串和若干次询问,你需要回答这些询问

每次询问格式为 \((l,r,s)\) ,询问若你初始有 \(s\) 分,按从左到右的顺序经历了 \([l,r]\) 这一子串的游戏结果后,最终分数是多少

分析

对于这种维护序列求和问题,一般可以用线段树解决,用 \(sum[k][t\bmod 3]\) 表示初始分为 \(t\) 时经历了 \(k\) 所代表的区间的游戏结果后增加的分数,具体实现可参考代码

代码
#include<bits/stdc++.h>
#define ls(k) k << 1
#define rs(k) k << 1 | 1
using namespace std;

const int MAX_N = 200000 + 5;
int n, q;
char s[MAX_N];
int sum[MAX_N * 4][3];

void push_up(int k)
{
    sum[k][0] = sum[ls(k)][0] + sum[rs(k)][(0 + sum[ls(k)][0]) % 3];
    sum[k][1] = sum[ls(k)][1] + sum[rs(k)][(1 + sum[ls(k)][1]) % 3];
    sum[k][2] = sum[ls(k)][2] + sum[rs(k)][(2 + sum[ls(k)][2]) % 3];
}

void build(int k, int l, int r)
{
    if(l == r) {
        if(s[l] == 'W') {
            sum[k][0] = sum[k][1] = sum[k][2] = 1;
        } else if(s[l] == 'D') {
            sum[k][0] = sum[k][1] = sum[k][2] = 0;
        } else {
            sum[k][1] = sum[k][2] = -1;
            sum[k][0] = 0;
        }
        return;
    }
    int m = (l + r) >> 1;
    build(ls(k), l, m);
    build(rs(k), m + 1, r);
    push_up(k);
}

int query(int k, int l, int r, int x, int y, int t)
{
    if(l >= x && r <= y) {
        return sum[k][t];
    }
    int m = (l + r) >> 1;
    int res = 0;
    if(x <= m)
        res += query(ls(k), l, m, x, y, (res + t) % 3);
    if(m + 1 <= y)
        res += query(rs(k), m + 1, r, x, y, (res + t) % 3);
    return res;
}

int main()
{
    scanf("%d%d%s", &n, &q, s + 1);
    build(1, 1, n);
    while(q--) {
        int l, r, x;
        scanf("%d%d%d", &l, &r, &x);
        printf("%d\n", x + query(1, 1, n, l, r, x % 3));
    }
    return 0;
}
posted @ 2022-01-25 17:28  f(k(t))  阅读(34)  评论(0编辑  收藏  举报