2020牛客暑期多校训练营(第七场)解题报告

2020牛客暑期多校训练营(第七场)

D. Fake News

题目大意

问你 ∑ i = 1 n i 2 \sum_{i=1}^{n}i^{2} i=1ni2是不是平方数

解题思路

打表发现只有当 n = 1 n=1 n=1 n = 24 n=24 n=24是是平方数

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while(T--) {
        ll n;
        cin >> n;
        if(n == 1 || n == 24) cout << "Fake news!\n";
        else cout << "Nobody knows it better than me!\n";
    }
    return 0;
}

B. Mask Allocation

题目大意

n × m n\times m n×m个物品,让你用 k k k个箱子把它们装起来,并且 k k k个箱子可以合并成 n n n个箱子装 m m m个物品,也可以合并成 m m m个箱子装 n n n个物品,让你使得 k k k最小,并且在满足 k k k的条件下满足字典序最大

解题思路

队友提供了一组样例
n = 16 n=16 n=16 m = 6 m=6 m=6的情况,我们使用如下规则:

箱子数物品数
126
44
42

手动模拟一下,发现这个答案是这么来的(以下 a a a代表箱子数, b b b代表物品数):
首先我们可以使得 a = n = 16 a=n=16 a=n=16 b = m = 6 b=m=6 b=m=6,那么此时显然可以满足 16 16 16 6 6 6的情况,我们需要拆/装箱使其满足 6 6 6 16 16 16

此时我们先拿出 6 6 6 6 6 6,我们还需要 6 6 6 10 10 10,还剩下 10 10 10 6 6 6
我们再拿出 6 6 6 6 6 6,我们还需要 6 6 6 4 4 4,还剩下 4 4 4 6 6 6
再拿出 4 4 4 4 4 4,我们还需要 2 2 2 4 4 4,还剩下 4 4 4 2 2 2
再拿出 2 2 2 2 2 2,我们还需要 2 2 2 2 2 2,还剩下 2 2 2 2 2 2
最后拿出 2 2 2 2 2 2

合并一下物品数相同的箱子,那么最终的答案就是 12 12 12 6 6 6 4 4 4 4 4 4 4 4 4 2 2 2

假设 a > b a>b a>b,也就是说我们不停的用 a a a减去 b b b,然后分出 b b b b b b,由于数据小于 1 e 4 1e^{4} 1e4,所以最多减 1 e 4 1e^{4} 1e4次, T < 100 T<100 T<100,复杂度大概为 O ( 1 e 6 ) O(1e^{6}) O(1e6)

AC代码
#include <bits/stdc++.h>
using namespace std;
vector<int> v;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--) {
        int n, m;
        cin >> n >> m;
        if (n < m) swap(n, m);
        int a = n, b = m;
        int res = 0;
        v.clear();
        while (true) {
            if (a == 0) break;
            if (a < b) swap(a, b);
            v.push_back(b);
            a -= b;
            res += b;
        }
        cout << res << '\n';
        sort(v.begin(), v.end(), greater<int>());
        int len = v.size();
        for (int i = 0; i < len; ++i) {
            int k = v[i];
            for (int j = 0; j < k; ++j) {
                cout << k << " ";
            }
        }
        cout << '\n';
    }
    return 0;
}

H. Dividing

题目大意

对于元组 ( n , k ) (n, k) (n,k),有如下定义:

  1. ∀ k \forall k k ( 1 , k ) (1, k) (1,k)为传奇元组
  2. 若元组 ( n , k ) (n, k) (n,k)为传奇元组,那么 ( n + k , k ) (n + k, k) (n+k,k)也为传奇元组
  3. 若元组 ( n , k ) (n, k) (n,k)为传奇元组,那么 ( n × k , k ) (n\times k, k) (n×k,k)也为传奇元组

现在给定 N N N K K K,问你 ∀ n ∈ [ 1 , N ] \forall n \in [1, N] n[1,N] ∀ k ∈ [ 1 , K ] \forall k \in [1, K] k[1,K],传奇元组 ( n , k ) (n, k) (n,k)的个数是多少

解题思路

首先从 ( 1 , k ) (1, k) (1,k)手动模拟,发现每次只有两次操作,将 n n n乘以 k k k或者将 n n n加上 k k k,那么任意一个传奇元组 ( n , k ) (n, k) (n,k),如果 n n n不是 k k k的倍数,那么我们可以将 n n n乘上一个 k k k使其变为 k k k的倍数,也就是说任意 k ∣ n k|n kn ( n , k ) (n, k) (n,k)为传奇元组,若你每次都加上一个 k k k,那么就会变成 ( t × k + 1 , k ) (t \times k+1,k) (t×k+1,k),此时 n n n k k k的倍数加一

也就是说当且仅当 ( n = 1 ) (n=1) (n=1) ( k ∣ n ) (k|n) (kn) ( k ∣ ( n − 1 ) ) (k|(n-1)) (k(n1)) ( n , k ) (n, k) (n,k)为传奇元组

那么对于给定的 n n n k k k,其个数为 ∑ i = 2 K [ N i ] + [ N − 1 i ] + N + K − 1 \sum_{i=2}^{K}[\frac{N}{i}]+[\frac{N-1}{i}]+N+K-1 i=2K[iN]+[iN1]+N+K1(加 N + K − 1 N+K-1 N+K1是因为 n = 1 n=1 n=1 k = 1 k=1 k=1的情况需要单独统计,然后减去重复的 ( 1 , 1 ) (1, 1) (1,1)

看一眼数据范围 1 ≤ N , K ≤ 1 0 12 1 \le N, K \le 10^{12} 1N,K1012,枚举 k k k显然会炸裂,继续观察上式,我们发现在某些块 i ∈ [ l , r ] i \in [l, r] i[l,r]之间, [ n i ] = m [\frac{n}{i}]=m [in]=m是固定的,其块区间求法如下:

假设有 [ n k 1 ] = m [\frac{n}{k_{1}}]=m [k1n]=m
n m = k 2 \frac{n}{m}=k_{2} mn=k2
n = m × k 2 n=m \times k_{2} n=m×k2
那么区间 ∀ k ∈ [ k 1 , k 2 ] \forall k \in [k_{1},k_{2}] k[k1,k2] [ n k ] = m [\frac{n}{k}]=m [kn]=m,因为 n = m × k 2 n=m \times k_{2} n=m×k2,表明 k 2 k_{2} k2一定是所求块的右区间,因为若 k > k 2 k>k_{2} k>k2,那么 m m m必定会减小

综上所述,我们使用分块思想对求和式进行优化,最终复杂度大概为 O ( k ) O(\sqrt{k}) O(k )

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    ll n, k;
    cin >> n >> k;
    ll res = (n + k - 1) % mod;
    for(ll i = 2; i <= k; ) {
        ll tmp = n / i;
        if(!tmp) break;
        ll l = i, r = min(n / tmp, k);
        res = (res + (r - l + 1) * tmp % mod) % mod;
        i = r + 1;
    }
    for(ll i = 2; i <= k; ) {
        ll tmp = (n - 1) / i;
        if(!tmp) break;
        ll l = i, r = min((n - 1) / tmp, k);
        res = (res + (r - l + 1) * tmp % mod) % mod;
        i = r + 1;
    }
    cout << res << '\n';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值