CSP-S 23

9.17

109=9+100

赛时将剩余时间“明智”的投入t4,结果就是后两题没分。

t1

三维前缀和+二分答案

写了个假的kdt调了2h...

二分显然是好想的。

看值域考虑复杂度: \(256\times 256\times 256\times 8=134217728\) 可过

所以三维前缀和。

其实想到这个就基本没什么问题了。

注意:为方便统计前缀和,将每个坐标均加1

code

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, K, up;
int sum[260][260][260], cnt[260][260][260];

inline bool check(int lim)
{
    for (int i = 1; i + lim <= up; ++i)
        for (int j = 1; j + lim <= up; ++j)
            for (int k = 1; k + lim <= up; ++k)
            {
                int x = i + lim, y = j + lim, z = k + lim;
                int cnt = sum[x][y][z] - sum[x][y][k - 1] - sum[i - 1][y][z] + sum[i - 1][y][k - 1] - sum[x][j - 1][z] + sum[i - 1][j - 1][z] + sum[x][j - 1][k - 1] - sum[i - 1][j - 1][k - 1];
                if (cnt >= K)
                    return 1;
            }
    return 0;
}

signed main()
{
    freopen("rgb.in", "r", stdin);
    freopen("rgb.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> K;
    for (int i = 1, x, y, z; i <= n; ++i)
    {
        cin >> x >> y >> z;
        ++x, ++y, ++z;
        ++cnt[x][y][z];
        up = max({x, y, z, up});
    }
    for (int i = 1; i <= up; ++i)
        for (int j = 1; j <= up; ++j)
            for (int k = 1; k <= up; ++k)
            {
                sum[i][j][k] = sum[i][j - 1][k] + sum[i - 1][j][k] - sum[i - 1][j - 1][k] + sum[i][j][k - 1] - sum[i - 1][j][k - 1] - sum[i][j - 1][k - 1] + sum[i - 1][j - 1][k - 1] + cnt[i][j][k];
            }

    int l = 0, r = up, ans = r;
    while (l <= r)
    {
        int mid = (l + r) >> 1;
        if (check(mid))
            r = mid - 1, ans = mid;
        else
            l = mid + 1;
    }
    cout << ans << "\n";
    return 0;
}

t2

很不理解为什么这题是t2

赛时t1 2h没写对已经以为要保单了,结果t2救回来了,30min切了。

题目都给你提示了...

观察到 \(m\le n^2\) ,不难想到同为 \(O(n^2)\) 的冒泡排序,进而发现这题就是手动冒泡排序...

具体的:

我们对于 \(1\le i \le n\) ,若 $a_i\neq b_i $ ,则在 \(a\) 数组中向后找到第一个值等于 \(b_i\) ,若找不到则无解,之后手动把它冒泡到 \(i\) 即可。

code

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int T, n, cnt;
int a[N], b[N];
pair<int, int> ans[N * N];

inline bool solve(int now)
{
    int id = 0;
    for (int i = now + 1; i <= n; ++i)
        if (b[now] == a[i])
        {
            id = i;
            break;
        }
    if (!id)
        return 0;
    for (int i = id; i > now; --i)
    {
        if (a[i - 1] <= a[i])
            return 0;
        swap(a[i - 1], a[i]);
        ans[++cnt] = make_pair(i - 1, i);
    }
    return 1;
}

signed main()
{
    freopen("sort.in", "r", stdin);
    freopen("sort.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> T;
    while (T--)
    {
        // cout << "T=" << T << "\n";
        memset(ans, 0, sizeof(ans));
        cnt = 0;
        cin >> n;
        for (int i = 1; i <= n; ++i)
            cin >> a[i];
        for (int i = 1; i <= n; ++i)
            cin >> b[i];
        bool bz1 = 1, bz2 = 1;
        for (int i = 1; i <= n; ++i) //-1
        {
            if (a[i + 1] < a[i])
                bz1 = 0;
            if (a[i] != b[i])
                bz2 = 0;
        }
        if (bz2)
        {
            cout << 0 << "\n"
                 << 0 << "\n";
            continue;
        }
        if (bz1)
        {
            cout << -1 << "\n";
            continue;
        }
        for (int i = 1; i <= n; ++i)
        {
            if (a[i] != b[i])
            {
                if (!solve(i))
                {
                    cout << -1 << "\n";
                    bz1 = 1;
                    break;
                }
            }
        }
        if (bz1)
            continue;
        if(cnt>n*n)
        {
            cout << -1 << "\n";
            continue;
        }
        cout << 0 << "\n";
        cout << cnt << "\n";
        for (int i = 1; i <= cnt;++i)
            cout << ans[i].first << ' ' << ans[i].second << "\n";
    }
    return 0;
}

t3

这题暴力是数位dp,正解就是推式子。

\(m\) 表示进制, \(n\) 表示位数。

我们考虑到对于每一种合法情况其中每一位数均小于 \(m\)

对于均小于 \(m\) 这一限制,直接做并不好求。

将其转化:

二项式反演

\(f(i)\) 表示至少有 \(i\) 位为 \(m\),\(g(i)\) 表示恰好有 \(i\) 位为 \(m\)

则答案只累加 \(f(0)\) .

\(f(0)=\sum_{i=0}^{n}(-1)^{i-0}\dbinom{i}{0}g(i)\)

即:

\(f(0)=\sum_{i=0}^{n}(-1)^i g(i)\)

\(dp_{i,j}\) 表示到 \(i\) 位,恰好有 \(j\) 位为 0 的方案数

所以 \(ans=\sum_{i=1}^ndp_{i,0}\)

考虑求 \(g(i)\) .

\(tot_i\) 表示第 \(i\) 位的后缀和 ,钦定 \(k\) 位为 \(m\),当前位填入 \(j\).

\(g(k)=\dbinom{n-i}{k}\dbinom{tot_i-j-k\times m+n-i-1}{n-i-1}\)

解释:

第一个组合数是考虑这 \(k\) 个数的位置。

第二个组合数使用插板法:

剩下的值为 \(tot_i-k\times m\)

对于剩下的 \(n-i-1\) 个数进行插板,因为可为0,所以值加上 \(n-i-1\)

所以最终 \(ans=\sum_{i=1}^n\sum_{j=0}^{a_i-1}\sum_{k=0}^{n-i}\dbinom{n-i}{k}\dbinom{tot_i-j-k\times m+n-i-1}{n-i-1}\)

这样转移即可97

(\(O(n^3)\)能97数据什么水平不评价)。

考虑优化:

观察发现对于最内层循环本质上是求杨辉三角中某一列的部分区间和,前缀和思想即可优化。

code

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
const int N = 2010;
const int M = N * N;
int n, m, tot;
int a[N];
int f[M], inv[M];

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

inline void init()
{
    f[0] = 1, inv[0] = 1;
    for (int i = 1; i < m * n; ++i)
    {
        f[i] = f[i - 1] * i % mod;
        inv[i] = km(f[i], mod - 2);
    }
}

inline int C(int a, int b)
{
    if (a < b)
        return 0;
    return f[a] * inv[b] % mod * inv[a - b] % mod;
}

signed main()
{
    freopen("dba.in", "r", stdin);
    freopen("dba.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> m >> n;
    init();
    for (int i = 1; i <= n; ++i)
        cin >> a[i], tot += a[i];
    int ans = 0;
    // 二项式反演
    for (int i = 1; i <= n; ++i)
    {
        for (int k = 0, op = 1; k <= n - i; ++k, op = -op) // 之后钦定k个为m
            ans = (ans + op * C(n - i, k) % mod * (C(tot - k * m + n - i, n - i) - C(tot - a[i] - k * m + n - i, n - i) + mod) % mod + mod) % mod;
        tot -= a[i]; // 后缀和
    }
    cout << (ans + mod) % mod << "\n";
    return 0;
}

t4

posted @ 2025-10-20 21:40  HS_fu3  阅读(12)  评论(0)    收藏  举报