2022.7.28 模拟赛

T1 数据结构

第一个操作很好实现,只需要增加 \(x^k\) 即可

第二个操作是瓶颈,暴力做是不行的

瓶颈在操作 \(2\),若是可以减少操作 \(2\) 的复杂度,就可以通过本题

怎么做呢?我们知道每个数到底被加了几次,就可以一次性算出它的贡献

我们每次操作 \(2\) 使用一个懒标记,加入 \(x\) 就是加入 \(x-tag\)

预处理组合数,如此一来复杂度就在 \(O(mk)\)

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
inline int rd()
{
    int ret = 0, f = 1;
    char c;
    while (c = getchar(), !isdigit(c))
        f = c == '-' ? -1 : 1;
    while (isdigit(c))
        ret = ret * 10 + c - '0', c = getchar();
    return ret * f;
}
const int MAXN = 55;
const int MOD = 1e9 + 7;
int k, m, sum[MAXN], f[MAXN][MAXN];
int s;
void solve()
{
    int x, y;
    y = rd();
    if (y == 0)
    {
        x = rd() - s;
        if (x < 0)
            x += MOD;
        int tmp = 1;
        for (int i = 0; i <= k; i++)
        {
            (sum[i] += tmp;) %= MOD;
            (tmp *= x) %= MOD;
        }
    }
    else
        s++;
    int ans = 0, tmp = 1;
    for (int i = k; i >= 0; i--)
    {
        (ans += (f[k][i] * sum[i]) % MOD * tmp % MOD) %= MOD;
        (tmp *= s) %= MOD;
    }
    cout << ans << endl;
}
signed main()
{
    m = rd();
    k = rd();
    f[0][0] = 1;
    for (int i = 1; i <= 50; i++)
    {
        f[i][0] = 1;
        for (int j = 1; j <= 50; j++)
        {
            f[i][j] = (f[i - 1][j] + f[i - 1][j - 1]) % MOD;
        }
    }
    while (m--)
        solve();
    return 0;
}

T2 商店购物

考虑一个判定问题:给出一个 \([k,2k]\) 判断是否可行。

对于每个元素 \(x\) :

\(x>2k\) ,则 \(x\) 对判定 \(k\) 无效

\(x∈[k,2k]\) ,则 \(k\) 一定可行

\(x<k\),需要继续讨论

我们关注小于 \(k\)\(x\) ,记它们的和 \(\sum_{x<k}{x}\)\(S\)

\(S<2k\),则 \(k\) 不可行

\(S∈[k,2k]\) ,则 \(k\) 可行(我们找到了一种方案)

\(S>2k\) ,则 \(k\) 可行

为什么?因为每个 \(x\) 均小于 \(k\) ,记 \(s'=s-x\)

\(S'>k\) 恒成立,故 \(S'\) 不可能小于 \(k\) 而使得 \(k\) 不可行

\(S'∈[k,2k]\),则 \(k\) 可行(我们找到了一种方案)

\(S'>2k\) ,那么可以继续重复此过程,直到可行

因此,只需要记录前缀和,算出所有的区间并,就是最终答案

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
const int MAXN = 100005;
int a[MAXN], n, ans;
signed main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + 1 + n);
    int sum = 0, lst = 0;
    for (int i = 0; i <= n; i++)
    {
        sum += a[i];
        while (a[i + 1] == a[i])
            sum += a[++i];
        ans += sum - max(lst, (a[i] + 1) / 2 - 1);
        lst = sum;
    }
    cout << ans << endl;
    return 0;
}

T3 植物大战僵尸

什么时候玩家必败?

如果存在形如 \(11,2222,33333333,...\) 的情况,那么玩家显然必败

也就是说,如果有 \(2^n\)\(n\) 位置的僵尸,那么玩家是必败的

我们发现,位置 \(n\) 的权重是 \(n-1\) 位置的一半,换句话说,后面最多只有一半的僵尸能到达下一个位置

我们可以把这个序列用每个元素的权重表示出来,如果我们认为 \(n\) 的权重是 \(2^n\) 的话,那么玩家必败当且仅当我们可以把序列分成两部分 \(A,B\),使得 \(A≥\frac{1}{2}\)\(B≥\frac{1}{2}\)

于是现在的问题就是,给定 \(n\) 个分数组成的集合,求把它们划分为两个 \(sum≥\frac{1}{2}\) 的集合的方案数

这样不好求,可以容斥一下,求把它们划分成至少有一个 \(sum<\frac{1}{2}\) 的方案数,这就是一个背包问题了

#include <algorithm>
#include <cstring>
#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
inline int rd()
{
    int ret = 0, f = 1;
    char c;
    while (c = getchar(), !isdigit(c))
        f = c == '-' ? -1 : 1;
    while (isdigit(c))
        ret = ret * 10 + c - '0', c = getchar();
    return ret * f;
}
void _(int x)
{
    if (x == 0)
        return;
    _(x / 10);
    putchar('0' + x % 10);
}
void out(int x)
{
    if (!x)
        return;
    _(x);
}
const int MAXN = 100005;
const int MOD = 1e9 + 7;
int T, n;
int a[MAXN], f[MAXN];
void init()
{
    memset(f, 0, sizeof(f));
}
int qpow(int x, int y)
{
    int ret = 1ll;
    while (y)
    {
        if (y & 1)
            (ret *= x) %= MOD;
        (x *= x) %= MOD;
        y >>= 1ll;
    }
    return ret;
}
void solve()
{
    n = rd();
    for (int i = 1; i <= n; i++)
        a[i] = rd();
    sort(a + 1, a + 1 + n);
    int V = 0, cur = a[n];
    f[0] = 1;
    for (int i = n; i >= 1; i--)
    {
        int t = 1;
        while (a[i] != cur)
        {
            t <<= 1;
            cur--;
            if (t > V)
            {
                cur = a[i];
                break;
            }
        }
        if (t > 1)
        {
            for (int k = 0; k <= V / t; k++)
            {
                int tmp = 0;
                for (int j = k * t; j < min((k + 1) * t, V + 1); j++)
                {
                    tmp += f[j];
                    f[j] = 0;
                    tmp %= MOD;
                }
                f[k] += tmp;
                f[k] %= MOD;
            }
            V /= t;
        }
        for (int j = V; j >= 0; j--)
            f[j + 1] += f[j], f[j + 1] %= MOD;
        V++;
    }
    int up = 1, ans = 0;
    while (cur > 1)
    {
        up <<= 1;
        up = min(up, V + 1);
        cur--;
    }
    for (int i = 0; i < up; i++)
        (ans += f[i]) %= MOD;
    ans = -2 * ans;
    ans += qpow(2, n);
    while (ans < 0)
        ans += MOD;
    out(ans);
    putchar('\n');
}
signed main()
{
    T = rd();
    while (T--)
        init(), solve();
    return 0;
}

T4 机房的新生活委员

\(f(i,j)\) 表示前 \(i\) 个商店买状态为 \(j\) 花的钱的最小值

则有转移方程 \(f(i,j)=min{f(i-1,j),f(i-1,j)+fee_i+min{f(i,p)+cost_{i,k}}}\)

初始状态 \(f(0,0)=0\) ,其他为 \(inf\)

最终答案为 \(f(n,2^m-1)\) , 时空复杂度为 \(O(nm2^m)\)

#include <cstdio>
#include <cstring>
using namespace std;
#define cmin(_a, _b) (_a > (_b) ? _a = (_b) : 0)
int fee[108], cost[108][18], tmp[108], f[66600], v[66600];
int main()
{
    register int n, m, i, j, S, x, _S, U, newf, newfS;
    scanf("%d %d", &n, &m);
    for (i = 1; i <= n; ++i)
    {
        scanf("%d", &fee[i]);
        for (j = 0; j < m; ++j)
            scanf("%d", &cost[i][j]);
    }

    U = (1 << m) - 1;
    for (S = 1; S <= U; ++S)
    {
        memcpy(tmp, fee, sizeof(tmp) / 108 * (n + 1));
        _S = S;
        for (i = 0; _S; ++i, _S >>= 1)
        {
            if ((_S & 1) == 0)
                continue;
            for (j = 1; j <= n; ++j)
                tmp[j] += cost[j][i];
        }
        v[S] = tmp[1];
        for (i = 2; i <= n; ++i)
            cmin(v[S], tmp[i]);
    }

    memset(f, 63, sizeof(f));
    f[0] = 0;
    for (S = 1; S <= U; ++S)
    {
        newfS = 0x3f3f3f3f;
        for (x = S; x; x = S & (x - 1))
        {
            newf = f[S ^ x] + v[x];
            cmin(newfS, newf);
        }
        f[S] = newfS;
    }
    printf("%d", f[U]);
    return 0;
}
posted @ 2022-07-28 16:52  PassName  阅读(50)  评论(0编辑  收藏  举报