Educational Codeforces Round 71 Div.2 解题报告 (CF1207)

D – Numbers of Permutations

思考量很小的一道题。

先用间接法来算。初始答案为\(n!\),然后考虑容斥掉那些符合排序规则的方案。发现,我们可以分开来考虑,按照容斥的顺序:先考虑只算第一维、第二维的,在加回来两维都考虑的。

第一维和第二维的做法非常显然:排序之后用多重集的排列就行了,考虑每一个相同的区间里面内部乘起来。如果有\(k\)个连续的数段,那么部分的答案就是\(\prod_{i = 1}^l len_k!\)。

两维皆有的情况稍微复杂一点。首先,如果按照第一维优先排序的方式,发现序列无法保证第二维单调不降,那么双维皆有的情况就不存在;反之,我们扫描第一维分出第一维的数段,然后在每一个数段里统计每一种数的个数,然后第三维就是这些「保证第一维相同的第二维数段」的答案。

// CF1207D.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 3e5 + 200, mod = 998244353;

pair<int, int> nodes[MAX_N];

int fac[MAX_N], n;

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &nodes[i].first, &nodes[i].second);
    for (int i = fac[0] = 1; i <= n; i++)
        fac[i] = 1LL * fac[i - 1] * i % mod;
    int ans[4] = {fac[n], 1, 1, 1};
    for (int c = 1; c <= 2; c++)
    {
        sort(nodes + 1, nodes + 1 + n);
        int l = 1, r;
        while (l <= n)
        {
            r = l + 1;
            while (r <= n && nodes[r].first == nodes[l].first)
                r++;
            ans[c] = 1LL * ans[c] * fac[r - l] % mod;
            l = r;
        }
        ans[0] = (1LL * ans[0] + 1LL * (mod - ans[c])) % mod;
        for (int i = 1; i <= n; i++)
            swap(nodes[i].first, nodes[i].second);
    }
    int l = 1, r;
    sort(nodes + 1, nodes + 1 + n);
    while (l <= n)
    {
        r = l + 1;
        while (r <= n && nodes[r].first == nodes[l].first)
            r++;
        map<int, int> mp;
        mp.clear();
        for (int i = l; i < r; i++)
            mp[nodes[i].second]++;
        for (map<int, int>::iterator it = mp.begin(); it != mp.end(); it++)
            ans[3] = 1LL * ans[3] * fac[(*it).second] % mod;
        l = r;
    }
    for (int i = 2; i <= n; i++)
        if (nodes[i].second < nodes[i - 1].second)
            ans[3] = 0;
    ans[0] = (1LL * ans[0] + 1LL * ans[3]) % mod;
    printf("%d", ans[0]);
    return 0;
}

E – XOR Guessing

sb 题。

考虑把一个 14 位的二进制数分为前 7 位和后 7 位。欲获取两段,保证每次询问时空出前七位或者后七位,然后拼起来就行了。

// CF1207E.cpp
#include <bits/stdc++.h>

using namespace std;

int main()
{
    fflush(stdout);
    cout << "? ";
    for (int i = 1; i < 100; i++)
        cout << i << " ";
    cout << 100 << endl;
    fflush(stdout);
    int ans1, ans2;
    cin >> ans1;
    fflush(stdout);
    cout << "? ";
    for (int i = 1; i < 100; i++)
        cout << (i << 7) << " ";
    cout << 12800 << endl;
    fflush(stdout);
    cin >> ans2;
    int mask1 = 0;
    for (int i = 0; i < 7; i++)
        mask1 |= (1 << i);
    int mask2 = mask1 << 7;
    cout << "! " << ((ans1 & mask2) | (ans2 & mask1)) << endl;
    return 0;
}

F – Remainder Problem

真的是暴力出奇迹啊,分块好题。

把问题简化一下,大概就是:

  1. 支持修改
  2. 给出块的大小\(x\),求出每一个块中块内下标为\(y\)的和。

首先,对于块大小\(x\)大的情况,回答询问只需要\(O(\lfloor \frac{n}{x} \rfloor)\)的时间就可以搞定了。如果块的大小比较小,比如说设置一个临界值\(k\),那么最多只会存在\(k^2\)级别的询问,除掉一个\(k\),也就说明每次修改只会影响到\(k\)个询问。所以,我们每次修改的时候对于每一个小于\(k\)的块长进行预处理,这样\(x\)小的时候就可以进行\(O(1)\)的回答了。

显然,\(k = \sqrt{n}\)。

// CF1207F.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 5e5 + 200, MAX_K = 750;

int sum[MAX_K][MAX_K], ai[MAX_N];

int main()
{
    int q;
    scanf("%d", &q);
    while (q--)
    {
        int opt, x, y;
        scanf("%d%d%d", &opt, &x, &y);
        if (opt == 1)
        {
            ai[x] += y;
            for (int i = 1; i < MAX_K; i++)
                sum[i][x % i] += y;
        }
        else if (x >= MAX_K)
        {
            int ans = 0;
            for (int i = y; i <= 500000; i += x)
                ans += ai[i];
            printf("%d\n", ans);
        }
        else
            printf("%d\n", sum[x][y]);
    }
    return 0;
}

Leave a Reply

Your email address will not be published. Required fields are marked *