「Fortuna OJ」Aug 18th – Group A 解题报告

A – 完全背包

这道题真的是令人窒息,也告诉了我比赛的时候上 QQ 是一个非常坏的习惯。

这道题 60 分做法非常之显然:相同体积的物品归为一种物品,且价值为同体积内最大的价值。这样就可以把物品数量压到 100 个以内。

但是这样是通过不了全部分数的,而且全部分数的容量非常之大,所以正常的 DP 优化是行不通的。做完上面的压缩之后,可以考虑做一些贪心进行优化。接下来我会详细的介绍这个贪心策略:

引理 1  给定任意的\(n\)个正整数,其中一定存在一个子集,其和为\(n\)的倍数。

证明  考虑设置前缀和:\[ S_i = \sum_{j = 1}^i a_j \]之后会发现,这个前缀和两两之间并不相同。考虑对\(S_i\)进行取模,然后有两种情况:

  • 如果存在两个\(S\)取模后的值相等,那么其差值取模之后的值就是\(0\),也就为\(n\)的倍数。
  • 如果不存在两个\(S\)取模后的值相等,那么一定存在取模后为\(0\)的前缀和,那么也为\(n\)的倍数。

证毕。

 

策略  对于一个性价比(\(rate_i = \frac{b_i}{a_i}\))最高的物品\(i\),剩下的\(x\)件物品,如果\(a_i \leq x\)时,肯定用\(a_i\)来替换掉\(x\)会更优。这是因为,\(a_i \leq x\)时,\(x\)件物品的组合一定会出现\(a_i\)的倍数,然后我们完全可以用性价比更高的物品\(i\)来替代掉这些组合获得更优的答案。我们用这个策略替代掉这些组合之后(可以换掉很多空间),我们就可以把剩下的空间搞出来进行暴力的完全背包就行。

// A.cpp
#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int MAX_N = 1e6 + 200;

ll n, m;
int volumes[MAX_N], weights[MAX_N], wgt[110];
int upper_tot, max_rate = 0, dp[2][MAX_N];
long double rate[110];

int main()
{
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d%d", &volumes[i], &weights[i]);
        wgt[volumes[i]] = max(wgt[volumes[i]], weights[i]);
        upper_tot = max(upper_tot, volumes[i]);
    }
    for (int i = 1; i <= upper_tot; i++)
    {
        rate[i] = (long double)wgt[i] / i;
        if (rate[i] > rate[max_rate])
            max_rate = i;
    }
    ll minus_part = m / max_rate - 100, answer = 0;
    if (minus_part > 0)
        m -= minus_part * max_rate, answer += wgt[max_rate] * minus_part;
    for (int i = 1; i <= upper_tot; i++)
        for (int j = 0; j <= m; j++)
        {
            dp[i & 1][j] = dp[(i - 1) & 1][j];
            if (j >= i)
                dp[i & 1][j] = max(dp[i & 1][j], dp[i & 1][j - i] + wgt[i]);
        }
    printf("%lld", dp[upper_tot & 1][m] + answer);
    return 0;
}

B – 中间值

这道题真的是分治板题(但是我分治是真的菜,根本不会写,还是需要进行加强啊)。

这道题保证了两个序列都是上升的,那么我们可以考虑做个分治来进行寻找。我们初始是寻找\(a[l_1 \dots r_1], b[l_2 \dots r_2]\)拼起来之后第\(k = \lfloor \frac{length}{2} \rfloor + 1\)个数。我们可以将\(k\)折半之后各放在这两个区间之中,假设为\(s, t\)。如果\(a_s > b_t\),就知道\(\{b_i\}\)中后半部分就是没用的部分了,亦而反之。大概就这样写写。

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

using namespace std;

const int MAX_N = 5e5 + 200;

int ai[MAX_N], bi[MAX_N], n, m;

namespace IO
{
const int MAXSIZE = 1 << 20;
char buf[MAXSIZE], *p1, *p2;
#define gc()                                                                 \
    (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin), p1 == p2) \
         ? EOF                                                               \
         : *p1++)
inline int rd()
{
    int x = 0, f = 1;
    char c = gc();
    while (!isdigit(c))
    {
        if (c == '-')
            f = -1;
        c = gc();
    }
    while (isdigit(c))
        x = x * 10 + (c ^ 48), c = gc();
    return x * f;
}
char pbuf[1 << 20], *pp = pbuf;
inline void push(const char &c)
{
    if (pp - pbuf == 1 << 20)
        fwrite(pbuf, 1, 1 << 20, stdout), pp = pbuf;
    *pp++ = c;
}
inline void write(int x)
{
    static int sta[35];
    int top = 0;
    do
    {
        sta[top++] = x % 10, x /= 10;
    } while (x);
    while (top)
        push(sta[--top] + '0');
}
} // namespace IO

inline void solve(int l1, int r1, int l2, int r2, int k)
{
    if (k == 1)
    {
        if (l1 > r1)
            printf("%d\n", bi[l2]);
        else if (l2 > r2)
            printf("%d\n", ai[l1]);
        else
            printf("%d\n", min(ai[l1], bi[l2]));
        return;
    }
    int s = l1 + (k >> 1) - 1, t = l2 + (k >> 1) - 1;
    if (s > r1)
        s = n + 1;
    if (t > r2)
        t = n + 1;
    if (ai[s] > bi[t])
        solve(l1, r1, l2 + (k >> 1), r2, k - (k >> 1));
    else
        solve(l1 + (k >> 1), r1, l2, r2, k - (k >> 1));
}

using namespace IO;

int main()
{
	freopen("median.in", "r", stdin);
	freopen("median.out", "w", stdout);
    n = rd(), m = rd();
    // scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; i++)
        // scanf("%d", &ai[i]);
        ai[i] = rd();
    for (int i = 1; i <= n; i++)
        // scanf("%d", &bi[i]);
        bi[i] = rd();
    ai[n + 1] = bi[n + 1] = 0x3f3f3f3f;
    while (m--)
    {
        int opt, x, y, z, l1, l2, r1, r2;
        // scanf("%d", &opt);
        opt = rd();
        if (opt == 1)
            x = rd(), y = rd(), z = rd(), ((x == 0) ? ai[y] : bi[y]) = z;
        else
            l1 = rd(), r1 = rd(), l2 = rd(), r2 = rd(), solve(l1, r1, l2, r2, (((r1 - l1 + 1) + (r2 - l2 + 1)) >> 1) + 1);
    }
    return 0;
}

C – Sequence

真你妈的毒瘤。

首先,\(f(x)\)显然是个积性函数,所以我们只需要知道如何计算素数幂的情况(形如\(p^k\))就行了。(这应该算是一种套路)

\(f(x)\)中包含了连续的\(gcd\),分解之后发现其实就是在做每一个素数指数的最小值,且题目给出了\(B\),所以最后肯定所有的指数的上界就是\(B\)中对应素因子的指数。

所以就是说,我们定\(p\)为其中的一个素数,定\(d\)为\(B\)中\(p\)的最大指数,也就是最大的\(p^d | B\),有:

\[ f(p_c) = \begin{cases} p_d(c – d + 1)^n + \sum_{i = 0}^{d – 1} [(c – i + 1)^n – (c – i)^1], c > d \\ \sum_{i = 0}^c p^i[(c – i + 1)^n – (c – i)^n], c \leq d \end{cases} \]

然后在线性筛的时候记录一些信息就行了。

// C.cpp
#include <bits/stdc++.h>
#define pr pair<int, int>
typedef long long ll;

using namespace std;

const int MAX_N = 2e7 + 200, mod = 998244353;

ll n, m, B;
int fi[MAX_N], prime[MAX_N], tot, pows[105], min_prime[MAX_N];
pr g[MAX_N];

int quick_pow(int bas, int tim)
{
    int ans = 1;
    while (tim)
    {
        if (tim & 1)
            ans = 1LL * ans * bas % mod;
        bas = 1LL * bas * bas % mod;
        tim >>= 1;
    }
    return ans;
}

void sieve()
{
    for (int i = 2; i <= m; i++)
    {
        if (!min_prime[i])
            prime[++tot] = min_prime[i] = g[i].second = i, g[i].first = 1;
        for (int j = 1; j <= tot && 1LL * i * prime[j] <= m; j++)
        {
            int tmp = prime[j] * i;
            g[tmp].first = 1, g[tmp].second = min_prime[tmp] = prime[j];
            if (i % prime[j] == 0)
            {
                g[tmp].first += g[i].first, g[tmp].second *= g[i].second;
                break;
            }
        }
    }
}

int main()
{
	freopen("sequence.in", "r", stdin);
	freopen("sequence.out", "w", stdout);
    scanf("%lld%lld%lld", &n, &m, &B);
    sieve();
    for (int i = 0; i <= 100; i++)
        pows[i] = quick_pow(i, n % (mod - 1));
    fi[1] = 1;
    for (int i = 2; i <= m; i++)
        if (g[i].second == i)
        {
            for (int j = 0, tmp = 1; j <= g[i].first; j++)
            {
                fi[i] = (1LL * fi[i] + 1LL * tmp * (1LL * pows[g[i].first - j + 1] - 1LL * pows[g[i].first - j] + mod) % mod) % mod;
                if ((B / tmp) % min_prime[i] == 0)
                    tmp *= min_prime[i];
            }
        }
        else
            fi[i] = 1LL * fi[i / g[i].second] * fi[g[i].second] % mod;
    for (int i = 1; i <= m; i++)
        fi[i] = (1LL * fi[i] + 1LL * fi[i - 1]) % mod;
    printf("%d\n", fi[m]);
    return 0;
}

Leave a Reply

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