Codeforces Round 970 (Div. 3)题解

题目地址

https://codeforces.com/contest/2008

锐评

本次D3的前四题还是比较简单的,没啥难度区分,基本上差不多,属于手速题。E的码量比F大一些,实现略显复杂一些。G的数学思维较明显,如果很久没有训练这个知识点,可能会一下子反应不过来,比如说我,需要花一点点时间观察,然后确认最优策略,整体不算太难,约等于D2的C题左右。H题较难,感觉原超过了D3 Rating范围,需要一定的优化技巧,但也不是不可做,思维量也没那么大。综合评估,个人觉得整场难度中等偏上了。

题解

Problem A. Sakurako’s Exam

题目大意

给定a(0≤a<10)a(0 \leq a \lt 10)a(0a<10)个整数111b(0≤b<10)b(0 \leq b \lt 10)b(0b<10)个整数222,每个整数可以选择不变或者变为它的相反数,问是否存在一种情况满足所有改变操作后,所有整数求和等于000

题解思路:二进制枚举/数学分类讨论

首先,a,ba,ba,b最大不过101010,最直接粗暴的方式是暴力枚举出每个数的可能性,然后求和判断即可,时间复杂度为O(2a+bmax⁡(a,b))O(2^{a + b}\max(a,b))O(2a+bmax(a,b))

采用上面的方式,好像有点冒进。当a,ba,ba,b都为999时,29+9×max⁡(9,9)=262144×9=23592962^{9 + 9} \times \max(9, 9) = 262144 \times 9 = 235929629+9×max(9,9)=262144×9=2359296,外面还套了个t(1≤t≤100)t(1 \leq t \leq 100)t(1t100),只给了1s1s1s的时限,极有可能会超时,好在过了,可能判定结束得早吧,勉强飘过。

重新来分析。222怎么消掉?只能用111个自己或者222111111呢?也只能用111个自己或者用自己222个去抵掉111222。观察发现111只能一对一对消失,所以当aaa为奇数时必然无解。因此aaa一定为偶数,所以它自己一定能相互抵消。我们考虑222怎么抵消,显然222自己相互抵消是最好的,它最多只会多出来111个,然后问111借两个就行,因此时间复杂度为O(1)O(1)O(1)

参考代码(C++)

二进制枚举代码

#include <bits/stdc++.h>
using namespace std;
int n, m;

void solve() {
    cin >> n >> m;
    for (int i = 0, len = 1 << n; i < len; ++i) {
        int sum = 0;
        for (int j = 0; j < n; ++j) {
            if ((i >> j) & 1)
                ++sum;
            else
                --sum;
        }
        for (int j = 0, siz = 1 << m; j < siz; ++j) {
            int sumt = 0;
            for (int k = 0; k < m; ++k) {
                if ((j >> k) & 1)
                    sumt += 2;
                else
                    sumt -= 2;
            }
            if (sumt + sum == 0) {
                cout << "YES\n";
                return;
            }
        }
    }
    cout << "NO\n";
}

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

数学分类讨论代码

#include <bits/stdc++.h>
using namespace std;
int n, m;

void solve() {
    cin >> n >> m;
    if (n & 1)
        cout << "NO\n";
    else {
        m &= 1;
        cout << (n >= m ? "YES\n" : "NO\n");
    }
}

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

Problem B. Square or Not

题目大意

给定一个长度为n(2≤n≤2⋅105)n(2 \leq n \leq 2 \cdot 10^5)n(2n2105)的字符串sss,仅包含字符000111

问能否将这个字符串变化为rrrccc列的二维矩阵,其中要求r=c且r×c=nr = c且r \times c = nr=cr×c=n,还要满足字符串依次从左到右从上到下填充方格后,最外层边界上的字符是111,不在最外层边界上的字符是000

题解思路:模拟 & 枚举

先判断nnn是不是一个平方数,如果不是,显然就没有答案。否则就枚举一遍,判断对应位置字符是不是符合要求即可,时间复杂度为O(n)O(n)O(n)

参考代码(C++)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 200'005;
int n;
string str;

void solve() {
    cin >> n >> str;
    int m = sqrt(n + 0.5);
    if (m * m != n)
        cout << "No\n";
    else {
        for (int i = 0; i < n; ++i) {
            int x = i / m, y = i % m;
            if (x == 0 || x == m - 1 || y == 0 || y == m - 1) {
                if (str[i] != '1') {
                    cout << "No\n";
                    return;
                }
            } else {
                if (str[i] != '0') {
                    cout << "No\n";
                    return;
                }
            }
        }
        cout << "Yes\n";
    }
}

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

Problem C. Longest Good Array

题目大意

构造一个严格单调递增的数组,并且从左到右相邻两个数的差也要是严格单调递增的。给定l,r(1≤l≤r≤109)l,r(1 \leq l \leq r \leq 10^9)l,r(1lr109),表示该数组元素的数据范围。问能构造的数组长度最大是多少?

题解思路:数学 & 枚举

因为要保持严格单调递增,不考虑数据范围上限,那么显然相邻差的下限为111,相邻差的序列形如1,2,3,4,⋯1,2,3,4,\cdots1,2,3,4,,显然这是一个公差为111等差数列,因为数组又要求严格单调递增,假如最多nnn项,根据等差数列求和公式,则有最大数为n(n−1)2\frac{n(n - 1)}{2}2n(n1)。显然,假如最大数据范围为limlimlim,构造的数组长度不会超过lim\sqrt{lim}lim。根据题目给定的数据范围10910^9109可知,第一个数为lll,然后直接枚举构造就可以了,时间复杂度为O(r−l+1)O(\sqrt{r - l + 1})O(rl+1)(不严格)。

参考代码(C++)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 200'005;
int a[maxn];
int l, r;
int n;

void solve() {
    cin >> l >> r;
    int id = 1, d = 1;
    a[0] = l;
    while (a[id - 1] + d <= r) {
        a[id] = a[id - 1] + d;
        ++id;
        ++d;
    }
    // cout << "res:" << a[id - 1] << '\n';
    cout << id << '\n';
}

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

Problem D. Sakurako’s Hobby

题目大意

给定一个长度为n(1≤n≤2⋅105)n(1 \leq n \leq 2 \cdot 10^5)n(1n2105)的排列p1,p2,⋯ ,pn(1≤pi≤n)p_1,p_2,\cdots,p_n(1 \leq p_i \leq n)p1,p2,,pn(1pin)

有一个操作,假如是第iii个位置,我们可以从位置iii跳跃到位置pip_ipi,即从位置iii可以到达位置pip_ipi,以此类推,我们可以重复跳跃过程到达某个位置。

再给定一个长度为nnn的字符串sss,仅包含字符000和字符111000表示黑色,111表示白色。字符串第iii个位置的字符s[i]s[i]s[i]表示上述排列第pip_ipi个位置为该字符,即为该位置的颜色。

求第i(1≤i≤n)i(1 \leq i \leq n)i(1in)个位置能到达的所有位置中位置颜色为黑色的有多少个?

题解思路:置换环 & 并查集

从一个点跳跃到另一个点,跳跃过的点就不能再跳跃,那么这个过程他肯定会终止,而且它会陷入一个循环。图论中叫置换环(有兴趣可以自行查阅)。我们可以模拟这个过程,然后一轮跳跃中的点为一组,他们可以互相到达。

那么怎么计算黑色个数呢?可以用并查集来解决这个问题,最终只需要计算出每个位置iii所在组的代表元的黑色个数,即是这一位置的答案,整体时间复杂度为O(n)O(n)O(n)(并查集接近线性,实际不是,跟阿克曼函数有关,这里并不严格)。

参考代码(C++)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 200'005;
struct dsu {
    int p[maxn], siz[maxn];
    int n;

    void init(int n) {
        this->n = n;
        for (int i = 0; i < n; ++i)
            p[i] = i, siz[i] = 1;
    }

    int findp(int x) {
        return p[x] == x ? x : p[x] = findp(p[x]);
    }

    bool unionp(int x, int y) {
        int fx = findp(x);
        int fy = findp(y);
        if (fx == fy)
            return false;
        if (siz[fx] >= siz[fy]) {
            p[fy] = fx;
            siz[fx] += siz[fy];
        } else {
            p[fx] = fy;
            siz[fy] += siz[fx];
        }
        return true;
    }

    bool same(int x, int y) {
        return findp(x) == findp(y);
    }

    int sizep(int x) {
        return siz[findp(x)];
    }
} d;
int a[maxn], cnt[maxn];
bool vis[maxn];
string str;
int n;

void solve() {
    cin >> n;
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        --a[i];
        vis[i] = false;
        cnt[i] = 0;
    }
    cin >> str;
    d.init(n);
    for (int i = 0; i < n; ++i)
        if (!vis[i]) {
            int p = i;
            do {
                vis[p] = true;
                d.unionp(i, p);
                p = a[p];
            } while (!vis[p]);
        }
    for (int i = 0; i < n; ++i)
        if (str[i] == '0')
            ++cnt[d.findp(a[i])];
    for (int i = 0; i < n; ++i)
        cout << cnt[d.findp(i)] << (" \n"[i == n - 1]);
}

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

Problem E. Alternating String

题目大意

交替串的定义:给定两个字符aaabbba,ba,ba,b可以相同),形如ababab⋯ababab\cdotsababab且长度为偶数的字符串。

现在给你一个长度为n(1≤n≤2⋅105)n(1 \leq n \leq 2 \cdot 10^5)n(1n2105)的字符串sss(只包含小写英文字母)。你可以进行如下两个操作。

1.删除某个位置上的字符(注意:此操作最多只能用一次)。

2.将某个位置上的字符替换为任意的小写英文字母。

问如何用最少的上述操作次数,使得该字符串为交替串?

题解思路:前后缀分解 & 前/后缀和 & 枚举

假如当前字符串长度为偶数,因为操作1最多只能用一次,而题目要求最终长度要为偶数,所以这种情况下就不能使用操作1,只能使用操作2。因此,我们只需要统计出奇数位置和偶数位置每个字母都分别有多少个,最后枚举将奇/偶数位置换成每个字母需要的操作次数取最小值即可。

假如当前字符串长度为奇数,因为操作1最多只能用一次,而题目要求最终长度要为偶数,所以这种情况下操作1就必须要使用了。因此,我们可以枚举删除的字符位置,然后将左右两边的字符串拼接起来,使用上面原始串长度为偶数一样的处理方式,将左右两边的计数汇总起来,然后枚举将奇/偶数位置换成每个字母需要的操作次数取最小值即可(注意,因为缺失了一个位置,所以这个位置后面位置所处的位置奇偶性发生了变化,计数要取对立位置的)。

为了处理简单些,我同时求了前缀和后缀和,整体时间复杂度为O(n)O(n)O(n)(忽略了小写英文字母个数26,实际是否能通过还是要考虑这个常数的)。

参考代码(C++)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 200'005;
const int mod = 1'000'000'007;
int cp[maxn][2][26], cs[maxn][2][26];
string str;
int n;

int qpow(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1)
            ans = 1LL * ans * a % mod;
        a = 1LL * a * a % mod;
        b >>= 1;
    }
    return ans;
}

void solve() {
    cin >> n >> str;
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < 26; ++j) {
            cp[i + 1][0][j] = cp[i][0][j];
            cp[i + 1][1][j] = cp[i][1][j];
        }
        ++cp[i + 1][i & 1][str[i] - 'a'];
    }
    for (int j = 0; j < 26; ++j)
        cs[n][0][j] = cs[n][1][j] = 0;
    for (int i = n - 1; i >= 0; --i) {
        for (int j = 0; j < 26; ++j) {
            cs[i][0][j] = cs[i + 1][0][j];
            cs[i][1][j] = cs[i + 1][1][j];
        }
        ++cs[i][(n - 1 - i) & 1][str[i] - 'a'];
    }
    int ans = n;
    if (n & 1) {
        for (int i = 0; i < n; ++i) {
            vector<vector<int>> cnt(2, vector<int>(26, 0));
            for (int j = 0; j < 26; ++j) {
                cnt[0][j] += cp[i][0][j];
                cnt[1][j] += cp[i][1][j];
            }
            for (int j = 0; j < 26; ++j) {
                cnt[0][j] += cs[i + 1][1][j];
                cnt[1][j] += cs[i + 1][0][j];
            }
            int maxe = 0, maxo = 0;
            for (int j = 0; j < 26; ++j) {
                maxe = max(maxe, cnt[0][j]);
                maxo = max(maxo, cnt[1][j]);
            }
            ans = min(ans, n - maxe - maxo);
        }
    } else {
        int maxe = 0, maxo = 0;
        for (int i = 0; i < 26; ++i) {
            maxe = max(maxe, cp[n][0][i]);
            maxo = max(maxo, cp[n][1][i]);
        }
        ans = min(ans, n - maxe - maxo);
    }
    cout << ans << '\n';
}

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

Problem F. Sakurako’s Box

题目大意

给定一个长度为n(2≤n≤105)n(2 \leq n \leq 10^5)n(2n105)的数组a1,a2,⋯ ,an(0≤ai≤109)a_1,a_2,\cdots,a_n(0 \leq a_i \leq 10^9)a1,a2,,an(0ai109)

问随机选择两个不同位置的数,求这两个数乘积的数学期望是多少?

题解思路:数学期望 & 费马小定理 & 前/后缀和

根据数学期望的定义,有下式。
E(x)=p12⋅(a1⋅a2)+p13⋅(a1⋅a3)+⋯+p(n−1)n⋅(an−1⋅an)=∑i=1n−1∑j=i+1npij⋅(ai⋅aj) \displaystyle E(x) = p_{12} \cdot (a_1 \cdot a_2) + p_{13} \cdot (a_1 \cdot a_3) + \cdots + p_{(n - 1)n} \cdot (a_{n - 1} \cdot a_n) = \sum_{i = 1}^{n - 1}\sum_{j = i + 1}^{n}p_{ij} \cdot (a_i \cdot a_j) E(x)=p12(a1a2)+p13(a1a3)++p(n1)n(an1an)=i=1n1j=i+1npij(aiaj)
pijp_{ij}pij都是相等的(因为概率相同),即为1Cn2\frac{1}{C_n^2}Cn21。合并同类项后,得到如下公式。
E(x)=1Cn2⋅(a1⋅∑i=2nai+a2⋅∑j=3naj+⋯+an−1⋅∑k=nnak) \displaystyle E(x) = \frac{1}{C_n^2} \cdot (a_1 \cdot \sum_{i = 2}^{n}a_i + a_2 \cdot \sum_{j = 3}^{n}a_j + \cdots + a_{n - 1} \cdot \sum_{k = n}^{n}a_k) E(x)=Cn21(a1i=2nai+a2j=3naj++an1k=nnak)
观察式子,很显然就是前/后缀和,问题得解。至于怎么消掉分数,可以看我上一篇文章《快速幂》,时间复杂度为O(n)O(n)O(n)(快速幂的指数是固定的,所以是常数)。

参考代码(C++)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 200'005;
const int mod = 1'000'000'007;
int a[maxn], suf[maxn];
int n;

int qpow(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1)
            ans = 1LL * ans * a % mod;
        a = 1LL * a * a % mod;
        b >>= 1;
    }
    return ans;
}

void solve() {
    cin >> n;
    for (int i = 0; i < n; ++i)
        cin >> a[i];
    suf[n - 1] = a[n - 1];
    for (int i = n - 2; i >= 0; --i)
        suf[i] = (suf[i + 1] + a[i]) % mod;
    int p = 0;
    for (int i = 1; i < n; ++i) {
        p += 1LL * a[i - 1] * suf[i] % mod;
        p %= mod;
    }
    p = (p << 1) % mod;
    int q = 1LL * n * (n - 1) % mod;
    cout << 1LL * p * qpow(q, mod - 2) % mod << '\n';
}

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

Problem G. Sakurako’s Task

题目大意

给定两个整数n,k(1≤n≤2⋅105,1≤k≤109)n,k(1 \leq n \leq 2 \cdot 10^5,1 \leq k \leq 10^9)n,k(1n2105,1k109),再给定一个长度为nnn的数组a1,a2,⋯ ,an(1≤ai≤109)a_1,a_2,\cdots,a_n(1 \leq a_i \leq 10^9)a1,a2,,an(1ai109)

你可以进行如下操作任意次数(只要满足条件可以一直进行下去):

选择两个不同的下标iiijjjai≥aja_i \geq a_jaiaj,然后对aia_iai进行赋值操作,ai=ai−aja_i = a_i - a_jai=aiaj或者ai=ai+aja_i = a_i + a_jai=ai+aj

你需要求出进行若干次操作后,这个数组缺失的第kkk小非负整数,并且尽可能让这个数最大,这个数最大是多少?

数组缺失的第kkk小非负整数是什么?举个例子,假如数组是[1,2,3][1,2,3][1,2,3],那么缺失的第1小非负整数是0,第2小非负整数是4;假如数组是[0,1,2,3,5,7][0,1,2,3,5,7][0,1,2,3,5,7],那么缺失的第1小非负整数是4,第2小非负整数是6。

题解思路:裴蜀定理 & 枚举

首先要使得这个数尽可能大,那么比较小的数应该尽可能地多。因此加法操作看起来好像没啥用哦。我们先只看减法操作,嘿,有点眼熟,好像更相减损术啊,那么是不是应该是求最大公约数

通过上面的分析,这个减法操作最终得到的数好像有迹可循,进而联想到裴蜀定理。我们先来看看裴蜀定理的定义。

对任意两个整数aaabbb,设ddd是它们的最大公约数。那么关于未知数xxxyyy的线性丢番图方程(称为裴蜀等式):ax+by=max + by = max+by=m有整数解(x,y)(x, y)(x,y)当且仅当mmmddd的整数倍。裴蜀等式有解时必然有无穷多个解。

推广到nnn个整数如下。

a1,⋯ ,ana_1,\cdots,a_na1,,annnn个整数,ddd是它们的最大公约数,那么存在整数x1,⋯ ,xnx_1,\cdots,x_nx1,,xn使得x1⋅a1+⋯+xn⋅an=dx_1 \cdot a_1 + \cdots + x_n \cdot a_n = dx1a1++xnan=d

上面定理说明了什么问题呢?它说明,对于两个数,无论你怎么操作,最终操作后的这个数它一定是这两个数的最大公约数的倍数,nnn个数也同样如此,这样就好办了。

通过上面的分析,我们应该让最大公约数尽可能小,这样它的倍数才能占据尽可能多的位置。而多个数的最大公约数只可能减小,不可能变大,所以我们只需要对所有数取最大公约数,这样就可以用有限次操作把所有数都变为最大公约数。其实这时候想象下,可以使用加法操作构造出首项为0,公差为最大公约数等差数列(假如数组长度为1,首项不能为0,因为没办法操作)。

最后,我们只需要枚举一下这个等差数列,看看空隙处能填多少个数,如果不够,往最后补齐即可。整体时间复杂度为O(nlogn)O(nlogn)O(nlogn)(主要是求最大公约数的部分,枚举部分时间复杂度为O(n)O(n)O(n))。

参考代码(C++)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 200'005;
const int mod = 1'000'000'007;
int a[maxn], b[maxn];
int n, m;

int qpow(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1)
            ans = 1LL * ans * a % mod;
        a = 1LL * a * a % mod;
        b >>= 1;
    }
    return ans;
}

void solve() {
    cin >> n >> m;
    int cd = 0;
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        cd = gcd(cd, a[i]);
    }
    b[0] = n == 1 ? cd : 0;
    for (int i = 1; i < n; ++i)
        b[i] = i * cd;
    int ans = 0, last = -1;
    for (int i = 0; i < n && m; ++i) {
        int cnt = b[i] - last - 1;
        int minv = min(cnt, m);
        m -= minv;
        ans = last + minv;
        last = b[i];
    }
    if (m)
        ans = last + m;
    cout << ans << '\n';
}

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

Problem H. Sakurako’s Test

题目大意

给定两个整数n,q(1≤n,q≤105)n,q(1 \leq n,q \leq 10^5)n,q(1n,q105),再给定一个长度为nnn的数组a1,a2,⋯ ,an(1≤ai≤n)a_1,a_2,\cdots,a_n(1 \leq a_i \leq n)a1,a2,,an(1ain)

对于给定的一个整数xxx,你可以进行如下操作任意次数(只要满足条件可以一直进行下去):选择一个下标iiiai≥xa_i \geq xaix,然后对aia_iai进行赋值操作,ai=ai−xa_i = a_i - xai=aix

问,给定qqq个这样的整数xxx,每行一个整数,你需要求进行若干次操作后,这个数组从小到大排序后的中位数最小可以是多少?

本题中位数定义为:对于偶数长度nnn,取第n+22\frac{n + 2}{2}2n+2个位置的数,对于奇数长度nnn,取第n+12\frac{n + 1}{2}2n+1个位置的数

题解思路:预处理 & 前缀和 & 二分 & 调和级数

要使得中位数最小,那么经过处理后的数组的值应该是越小越好。根据操作的定义,最终对每个数执行若干次操作后的实际效果其实就是ai=ai mod xa_i = a_i \bmod xai=aimodx

本题的时限仅仅为1s,而q,nq,nq,n最大都可以为10510^5105。显然,对于时间复杂度超过O(qn)O(qn)O(qn)的代码都不足以通过此题(例如每个数都进行取模操作,然后排序,时间复杂度为O(qnlogn)O(qnlogn)O(qnlogn))。

怎么优化呢?qqq我们肯定改变不了,毕竟读入就要qqq。因此,我们只能寄希望于降低循环体内的查询时间。因为当前查询的是xxx,那么答案是多少呢?显然,答案在区间[0,x−1][0,x - 1][0,x1]内,因为取模后所有数肯定是小于xxx的。我们知道排序后,数字都是从小到大的,那么中位数是否具有单调性呢?答案是肯定的。为什么呢?因为某个数yyy是中位数,意味着小于等于y−1y - 1y1的元素个数要小于上面中位数定义的位置,且小于等于yyy的元素个数要大于等于上面中位数定义的位置(第一个条件如果是大于等于,说明中位数位置被占了,yyy肯定不在那个位置上,显然不可能是中位数;第二个条件如果是小于,那就说明,yyy都不够填充到中位数的位置,中位数显然最小是y+1y + 1y+1),所以可以二分答案。

根据上面的分析,时间复杂度好像是O(qlogx)O(qlogx)O(qlogx),有戏。咦?好像又不太对,我们必须用O(1)O(1)O(1)时间复杂度检查出上面提到的计数问题是否合法。nnn个数,O(1)O(1)O(1)?逗我,臣妾做不到啊!先放弃,考虑下这个问题的普通做法,最朴素的当然是一个一个枚举,显然不可行!我们注意到,对于小于等于某个数yyy的元素个数,当元素数据范围限定在某个区间内,我们可以用空间换取时间。例如,本题1≤ai≤n1 \leq a_i \leq n1ain,我们用cnticnt_icnti表示数组中有多少个数等于iii,那么我们把小于等于iii的计数加起来就是整个数组中小于等于iii的元素个数,该问题可以用前缀和线性解决。

好像还是没啥用啊?问题依旧没解决。别急,我们继续看。对于题目中每个询问的xxx,其实将nnn分为了很多类似上面前缀和计数的块,其中每个块求小于等于某个数的个数可以O(1)O(1)O(1)求出来,请看如下规律。
0,1,⋯ ,x−1,x,x+1,⋯ ,2⋅x−1,2⋅x,2⋅x+1,⋯ ,3⋅x−1,3⋅x,3⋅x+1,⋯ 0,1,\cdots,x - 1,x,x + 1,\cdots,2 \cdot x - 1,2 \cdot x,2 \cdot x + 1,\cdots,3 \cdot x - 1,3 \cdot x,3 \cdot x + 1,\cdots 0,1,,x1,x,x+1,,2x1,2x,2x+1,,3x1,3x,3x+1,
xxx取模后,得到如下规律。
0,1,⋯ ,x−1,0,1,⋯ ,x−1,0,1,⋯ ,x−1,0,1,⋯ 0,1,\cdots,x - 1,0,1,\cdots,x - 1,0,1,\cdots,x - 1,0,1,\cdots 0,1,,x1,0,1,,x1,0,1,,x1,0,1,
根据上面的规律,对于每个要查询的xxx,我们将nnn分成了⌈nx⌉\lceil \frac{n}{x} \rceilxn块(向上取整)。对于每一块可以用前缀和O(1)O(1)O(1)时间内计算出小于等于某个数的元素个数。

合并截至目前的分析结果,得到时间复杂度为O(q⋅logx⋅nx)O(q \cdot logx \cdot \frac{n}{x})O(qlogxxn)。这个复杂度十分依赖测试数据给定的xxx,出题人肯定没那么友好,最简单的10510^5105x=1x = 1x=1就卡死了,况且还有Hack阶段。测试一下,果然超时了,后面也给出代码供参考。

上面测试数据全是111的猜想给出了一个优化方向,考虑到有大量重复的计算,某一个xxx算过了,再给定同样的xxx又重新算了一遍。那么我们何不一次性计算出所有答案,即预处理所有可能的xxx,对于每个查询直接O(1)O(1)O(1)输出答案。因为1≤x≤n1 \leq x \leq n1xn,每个xxx计算的时间复杂度为O(logx⋅nx)O(logx \cdot \frac{n}{x})O(logxxn),故总的计算量为∑i=1n(logi⋅ni)\displaystyle\sum_{i = 1}^{n}(logi \cdot \frac{n}{i})i=1n(logiin)。公式有点眼熟,哦!原来是调和级数!依据如下。
∑i=1∞1i=1+12+13+⋯ \displaystyle\sum_{i = 1}^{\infty}\frac{1}{i} = 1 + \frac{1}{2} + \frac{1}{3} + \cdots i=1i1=1+21+31+
调和级数的第nnn项部分和为:
∑i=1n1i=1+12+13+⋯+1n \displaystyle\sum_{i = 1}^{n}\frac{1}{i} = 1 + \frac{1}{2} + \frac{1}{3} + \cdots + \frac{1}{n} i=1ni1=1+21+31++n1
也叫作第nnn个调和数。第nnn个调和数与nnn的自然对数的差值(即∑i=1n1i−ln⁡n\displaystyle\sum _{i=1}^{n}\frac{1}{i}-\ln ni=1ni1lnn)收敛于常数(欧拉-马歇罗尼常数)。

综上所述,整体时间复杂度为O(q+n⋅ln⁡n⋅logn)O(q + n \cdot \ln n \cdot logn)O(q+nlnnlogn)1s1s1s可过。

参考代码(C++)

超时代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100'005;
const int mod = 1'000'000'007;
int cnt[maxn];
int n, q;

int qpow(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1)
            ans = 1LL * ans * a % mod;
        a = 1LL * a * a % mod;
        b >>= 1;
    }
    return ans;
}

int calc(int lim, int step) {
    if (lim < 0)
        return 0;
    int ans = 0, lastc = 0;
    for (int i = 0; i <= n; i += step) {
        int j = min(i + lim, n);
        ans += cnt[j] - lastc;
        lastc = cnt[min(i + step - 1, n)];
    }
    return ans;
}

void solve() {
    cin >> n >> q;
    for (int i = 1; i <= n; ++i)
        cnt[i] = 0;
    int x, id = n >> 1;
    for (int i = 0; i < n; ++i) {
        cin >> x;
        ++cnt[x];
    }
    for (int i = 1; i <= n; ++i)
        cnt[i] += cnt[i - 1];
    for (int i = 0; i < q; ++i) {
        cin >> x;
        int l = 0, r = x - 1, ans = -1;
        while (l <= r) {
            int mid = (l + r) >> 1;
            int cl = calc(mid - 1, x), ce = calc(mid, x);
            if (cl <= id && ce > id) {
                ans = mid;
                r = mid - 1;
            } else if (cl > id)
                r = mid - 1;
            else
                l = mid + 1;
        }
        cout << ans << (" \n"[i == q - 1]);
    }
}

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

通过代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100'005;
const int mod = 1'000'000'007;
int cnt[maxn], ans[maxn];
int n, q;

int qpow(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1)
            ans = 1LL * ans * a % mod;
        a = 1LL * a * a % mod;
        b >>= 1;
    }
    return ans;
}

int calc(int lim, int step) {
    if (lim < 0)
        return 0;
    int ans = 0, lastc = 0;
    for (int i = 0; i <= n; i += step) {
        int j = min(i + lim, n);
        ans += cnt[j] - lastc;
        lastc = cnt[min(i + step - 1, n)];
    }
    return ans;
}

void solve() {
    cin >> n >> q;
    for (int i = 1; i <= n; ++i)
        cnt[i] = 0;
    int x, id = n >> 1;
    for (int i = 0; i < n; ++i) {
        cin >> x;
        ++cnt[x];
    }
    for (int i = 1; i <= n; ++i)
        cnt[i] += cnt[i - 1];
    for (int i = 1; i <= n; ++i) {
        int l = 0, r = i - 1, res = -1;
        while (l <= r) {
            int mid = (l + r) >> 1;
            int cl = calc(mid - 1, i), ce = calc(mid, i);
            if (cl <= id && ce > id) {
                res = mid;
                r = mid - 1;
            } else if (cl > id)
                r = mid - 1;
            else
                l = mid + 1;
        }
        ans[i] = res;
    }
    for (int i = 0; i < q; ++i) {
        cin >> x;
        cout << ans[x] << (" \n"[i == q - 1]);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t = 1;
    cin >> t;
    while (t--)
        solve();
    return 0;
}
为了测算上市公司专利质量,本文通过分析公司所申请专利的主分类号,并采用知识宽度来衡量专利质量。中国的IPC分类号采用“部-大类-小类-大组-小组”的格式,如“A01B01/00”。若仅根据专利分类号数量评估专利质量可能存在偏差,因此本文参考赫芬达尔指数计算企业在不同大组下的专利分布,定义知识宽度。该值越大,专利质量越高。数据处理时只选取发明和实用新型专利,并剔除了无效的专利。该数据包括原始数据、dofile、整理结果、参考文献,数据由马克社区用户上传,供大家参考参考文献:李宏,王云廷,吴东松.专利质量对企业出口竞争力的影响机制:基于知识宽度视角的探究[J].世界经济研究,2021,(01):32-46+134.一、数据介绍数据名称:上市公司专利质量数据-原始+stata代码+结果数据年份:1990-2023年样本数量:4万+数据格式:面板数据二、指标说明上市公司专利质量测算结果的指标包括:股票代码 年份 申请专利质量 授权专利质量上市公司专利质量数据-原始+stata代码+结果三、数据文件发明授权面板数据.dta;发明申请面板数据.dta;实用新型授权面板数据.dta;实用新型申请面板数据.dta;上市公司专利质量测算代码.do;上市公司专利原始数据.zip;上市公司专利质量最终结果.dta;专利质量对企业出口竞争力的...制_基于知识宽度视角的探究_李宏.pdf
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值