Teza Round 1 (Codeforces Round 1015, Div. 1 + Div. 2)


A. Max and Mod

题意:构造一个排列,使得\(\max(p_i, p_{i-1}) = i - 1\)

发现\(n\)为奇数时输出\(n, 1, 2, ... , n - 1\)就行。
如果为偶数,无解。(猜的)

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    if (n % 2 == 0) {
    	std::cout << -1 << "\n";
    } else {
    	std::cout << n << " ";
    	for (int i = 1; i < n; ++ i) {
    		std::cout << i << " \n"[i == n - 1];
    	}
    }
}

B. MIN = GCD

题意:给你一个数组,重新排列后使得数组可以分成两部分,前一部分的最小值和后一部分的\(gcd\)相等。

不管最小值在哪一个部分,这一部分的值都小于等于它。
然后\(gcd\)肯定小于等于其中的最小值,所以我们应该放至少一个最小值在前面。然后后面都放最小值的倍数。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<i64> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    std::ranges::sort(a);
    i64 d = 0;
    for (int i = 1; i < n; ++ i) {
    	if (a[i] % a[0] == 0) {
    		d = std::gcd(d, a[i]);
    	}
    }

    if (d == a[0]) {
    	std::cout << "YES\n";
    } else {
    	std::cout << "NO\n";
    }
}

C. You Soared Afar With Grace

题意:给你两个排列,你每次交换\(a_i, a_j\)\(b_i, b_j\)。求能不能使得\(a\)\(b\)是反着的。

单独考虑\(a\)\(b\)都无法解决问题。不妨把两个数组看作一个数组\(c\)\(c_i = \{a_i, b_i\}\)。那么显然我们需要让\(\{a_i, b_i\}\)\(\{b_i, a_i\}\)出现在对称的位置。
判断一些无解的情况后模拟就行。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n), b(n);
    for (int i = 0; i < n; ++ i) {
        std::cin >> a[i];
        -- a[i];
    }

    for (int i = 0; i < n; ++ i) {
        std::cin >> b[i];
        -- b[i];
    }

    int cnt = 0;
    for (int i = 0; i < n; ++ i) {
        cnt += a[i] == b[i];
    }

    if (cnt > n % 2) {
        std::cout << -1 << "\n";
        return;
    }

    std::vector<std::pair<int, int>> ans;
    for (int i = 0; i < n; ++ i) {
        if (a[i] == b[i]) {
            std::swap(a[i], a[n / 2]);
            std::swap(b[i], b[n / 2]);
            if (i != n / 2) {
                ans.emplace_back(i, n / 2);
            }
            break;
        }
    }

    std::map<std::pair<int, int>, int> p;
    for (int i = 0; i < n; ++ i) {
        p[{a[i], b[i]}] = i;
    }

    for (int i = 0; i < n / 2; ++ i) {
        if (!p.count({b[i], a[i]})) {
            std::cout << -1 << "\n";
            return;
        }

        int j = p[{b[i], a[i]}];
        if (j == n - 1 - i) {
            continue;
        }
        std::swap(p[{b[i], a[i]}], p[{a[n - 1 - i], b[n - 1 - i]}]);
        std::swap(a[n - 1 - i], a[j]);
        std::swap(b[n - 1 - i], b[j]);
        ans.emplace_back(n - 1 - i, j);
    }

    for (int i = 0; i < n; ++ i) {
        if (a[i] != b[n - 1 - i]) {
            std::cout << -1 << "\n";
            return;
        }
    }

    std::cout << ans.size() << "\n";
    for (auto & [i, j] : ans) {
        std::cout << i + 1 << " " << j + 1 << "\n";
    }
}

D. Arcology On Permafrost

题意:如果给你一个数组\(a\),你需要让它的\(mex\)最小,每次可以删除它的一个长度为\(k\)的子数组,最大操作\(m\)次。现在给你\(n, m, k\),要求你构造一个数组,使得操作后\(mex\)是所有长度为\(n\)的数组里最大的。

如果答案是\(d\),数组一定是\(0, 1, 2 ... d, 0, 1, 2.. d, ..\)这样。
怎么找到这个\(d\)?发现会删除\(m\)次,那么\([1, d]\)的每个数至少出现\(m + 1\)次。于是\(d\)最大是\(\lfloor \frac{n}{m+1} \rfloor\)
然后发现还有\(k\)没考虑,也就是说\(0, 1, 2 .. d, .. 0\)每个相同数之间至少间隔\(\max(d, k)\)。那么就这样构造能得到最大。

点击查看代码
void solve() {
    int n, m, k;
    std::cin >> n >> m >> k;
    std::vector<int> ans(n);
    
    int d = n / (m + 1);
    for (int i = 0; i < d; ++ i) {
    	for (int j = i; j < n; j += std::max(k, d)) {
    		ans[j] = i;
    	}
    }

    for (int i = 0; i < n; ++ i) {
    	std::cout << ans[i] << " \n"[i == n - 1];
    }
}

E. Blossom

题意:一个\([0, n - 1]\)的排列,有些数是\(-1\)代表没有给出。求所有排列的所有子区间的\(mex\)之和。

如果我们枚举\(mex\),并且求所有大于等于当前\(mex\)的区间数,那么本来一个\(mex\)\(k\)的区间贡献是\(k\),我们把它拆成了\(k\)\(1\)的贡献,那么我们只需要枚举\(mex\),那么所有大于等于当前\(mex\)的区间数都会有\(1\)的贡献。
考虑\(mex\)\(k\),我们需要考虑\(-1\)带来的影响,那么设当前区间有\(c1\)\(-1\),需要填上\(c2\)\(-1\)使得\(mex\)至少为\(k\),然后总共有\(c3\)\(-1\),那么这个区间的贡献为\(C(c1, c2) \times c2! \times (c3 - c2)!\),意味\(c1\)个里选出\(c2\)个填充\(mex\),然后这\(c2\)个位置怎么填无所谓,只需要让\(mex = k\)就行,所以有\(c2!\)中方案,同时剩下\(c3 - c2\)\(-1\)也是随便排,所以有\((c3 - c2)!\)的方案。然后化简(其实化不化无所谓):\(C(c1, c2) \times c2! \times (c3 - c2)! = \frac{c1!}{c2!(c1 - c2)!} \times c2! \times (c3 - c2)! = \frac{c1!}{(c1-c2)!}\times (c3-c2)!\),这样只是可以不用算组合数。
然后计算\(cnt_{-1} = c1\)的区间有多少个,这个可以预处理算出来。那么我们可以枚举\(mex\)同时枚举\(c1\),但这样会算不对,因为我们还需要满足,如果\(mex = k\),那么\([0, mex - 1]\)的数要么没出现,要么在当前区间里,显然会多算。那么考虑删掉这些区间的贡献,假设之前已经得到满足包含\([0, k - 1]\)的区间的左边界为\(l\),右边界为\(r\),那么之前已经把不包含\([l, r]\)的区间都删掉了,考虑\(pos_{k}\)的位置,如果\(pos_k < l\),那么需要把包含了\([l, r]\)但不包含\([pos_k, r]\)的区间删掉,\(pos_k > r\)同样处理。这样枚举区间一开始都被加入一次,每个\(k\)删去的区间都是不一样的,那么每个区间只会被删除一次,复杂度得到了保证。
代码省略取模类。

点击查看代码
struct Comb {
    int n;
    std::vector<Z> _fac;
    std::vector<Z> _invfac;
    std::vector<Z> _inv;
    
    Comb() : n{0}, _fac{1}, _invfac{1}, _inv{0} {}
    Comb(int n) : Comb() {
        init(n);
    }
    
    void init(int m) {
        if (m <= n) return;
        _fac.resize(m + 1);
        _invfac.resize(m + 1);
        _inv.resize(m + 1);
        
        for (int i = n + 1; i <= m; i++) {
            _fac[i] = _fac[i - 1] * i;
        }
        _invfac[m] = _fac[m].inv();
        for (int i = m; i > n; i--) {
            _invfac[i - 1] = _invfac[i] * i;
            _inv[i] = _invfac[i] * _fac[i - 1];
        }
        n = m;
    }
    
    Z fac(int m) {
        if (m > n) init(2 * m);
        return _fac[m];
    }
    Z invfac(int m) {
        if (m > n) init(2 * m);
        return _invfac[m];
    }
    Z inv(int m) {
        if (m > n) init(2 * m);
        return _inv[m];
    }
    Z binom(int n, int m) {
        if (n < m || m < 0) return 0;
        return fac(n) * invfac(m) * invfac(n - m);
    }
} comb;

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n + 1);
    for (int i = 1; i <= n; ++ i) {
        std::cin >> a[i];
    }

    std::vector<int> pos(n), sum(n + 1);
    for (int i = 1; i <= n; ++ i) {
        sum[i] = sum[i - 1];
        if (a[i] == -1) {
            ++ sum[i];
        } else {
            pos[a[i]] = i;
        }
    }

    std::vector<int> tot(n + 1);
    for (int i = 1; i <= n; ++ i) {
        for (int j = i; j <= n; ++ j) {
            ++ tot[sum[j] - sum[i - 1]];
        }
    }

    Z ans = 0;
    int cnt = 0, l = -1, r = -1;
    for (int x = 0; x < n; ++ x) {
        if (pos[x] != 0) {
            if (l == -1) {
                for (int i = 1; i < pos[x]; ++ i) {
                    for (int j = i; j < pos[x]; ++ j) {
                        -- tot[sum[j] - sum[i - 1]];
                    }
                }

                for (int i = pos[x] + 1; i <= n; ++ i) {
                    for (int j = i; j <= n; ++ j) {
                        -- tot[sum[j] - sum[i - 1]];
                    }
                }
                l = r = pos[x];
            } else if (pos[x] < l) {
                for (int i = pos[x] + 1; i <= l; ++ i) {
                    for (int j = r; j <= n; ++ j) {
                        -- tot[sum[j] - sum[i - 1]];
                    }
                }
                l = pos[x];
            } else if (pos[x] > r) {
                for (int i = 1; i <= l; ++ i) {
                    for (int j = r; j < pos[x]; ++ j) {
                        -- tot[sum[j] - sum[i - 1]];
                    }
                }
                r = pos[x];
            }
        } else {
            ++ cnt;
        }
        for (int k = cnt; k <= n; ++ k) {
            ans += tot[k] * comb.fac(k) * comb.invfac(k - cnt) * comb.fac(sum[n] - cnt);
        }
    }
    std::cout << ans << "\n";
}
posted @ 2025-04-06 01:51  maburb  阅读(330)  评论(0)    收藏  举报