雅礼国庆集训 day1 T1 养花

题面

题目下载

算法

考虑当 \(k\) 确定的时候如何求答案,
显然对于所有形如 \([ak, (a+1)k)\) 的值域区间, 最大值一定是最优的

似乎怎么都是 \(O(n^2)\) 的算法
观察到 \(a_i\) 的值域比较小, 所以考虑桶
显然对于一段区间 \([L, R]\)
我们可以推出其 \(mod k\) 的最大值

  • 方法
    首先用一个数组 \(f_i (\forall i \leq 10^5)\) 表示比 \(i\) 小的最大的数
    这里有一个 \(\rm{unbelievable}\) 的递推方法( \(O(N + n)\) )
    for (int i = l; i <= r; i++)
        f[a[i]] = a[i];
    for (int i = 1; i <= N; i++) // N 为值域
        f[i] = std::max(f[i], f[i - 1]);

接下来可以通过这一递推公式求出这段区间对 \(i\) 取余的最大值

\[g_i = \max (f_{i \times j - 1} \mod i), i \times j \leq N \]

(这里注意 \(j\) 得从 \(1\) 开始取)

在代码中的实现优化掉了 \(mod\) 运算

对于每一个块
计算是调和级数的复杂度, 也即 \(O(N \ln N)\)

但是此时计算单个区间的时间复杂度仍然是 \(10^5\) 级别的, 不可能在乘以一个 \(m\)
考虑分块, 这样能在 \(O(m \sqrt{n} + n \sqrt{n} \log n)\) 的复杂度下草过去(然而草不过去, 会TLE)

由此分析分块的最优复杂度

分块的时间复杂度主要取决于分块的块长,一般可以通过均值不等式求出某个问题下的最优块长,以及相应的时间复杂度

由于时间复杂度的总和是

\[O(\frac{n}{Size}N \ln N + mSize) = O(\frac{n ^ 2 \ln n}{Size} + mSize) \]

均值不等式求得
\(Size\) 最优为 \(\sqrt{n \ln n}\)
复杂度为 \(O(n \sqrt{n \ln n})\), 当然常数拉满了

为什么正解这么暴力

代码

#include <bits/stdc++.h>
const int N = 1e5;
const int MAXN = 2e5 + 20;
const int MAXBLOCKSIZE = 500;

int n, m;
int a[MAXN];

void solve1()
{
    while (m--)
    {
        int l, r, k;
        scanf("%d %d %d", &l, &r, &k);

        int Ans = 0;
        for (int i = l; i <= r; i++)
        {
            Ans = std::max(Ans, a[i] % k);
        }

        printf("%d\n", Ans);
    }
}

struct block
{
    int Max_Num_Before[MAXN];
    int Max_Mod[MAXN];

    int Size;
    int start, end;
} Block[MAXBLOCKSIZE];
int pos[MAXN];

void Calc(int i);

void Split_and_Init()
{
    int block_Size = sqrt(n * log(n));
    int block_Num = n / block_Size;
    if (n % block_Size)
        block_Num++;

    for (int i = 1; i < block_Num; i++)
    {
        Block[i].start = (i - 1) * block_Size + 1;
        Block[i].end = i * block_Size;
        Calc(i);
    }

    Block[block_Num].start = (block_Num - 1) * block_Size + 1;
    Block[block_Num].end = n;
    Calc(block_Num);

    for (int i = 1; i <= n; i++)
    {
        pos[i] = (i - 1) / block_Size + 1;
    }
}

int Find(int l, int r, int k)
{
    int Left_Block = pos[l];
    int Right_Block = pos[r];
    int Ans = 0;
    if (Left_Block == Right_Block)
    {
        for (int i = l; i <= r; i++)
        {
            Ans = std::max(Ans, a[i] % k);
        }
        return Ans;
    }
    else
    {
        for (int i = Left_Block + 1; i <= Right_Block - 1; i++)
        {
            Ans = std::max(Ans, Block[i].Max_Mod[k]);
        }

        for (int i = l; i <= Block[Left_Block].end; i++)
        {
            Ans = std::max(Ans, a[i] % k);
        }
        for (int i = Block[Right_Block].start; i <= r; i++)
        {
            Ans = std::max(Ans, a[i] % k);
        }

        return Ans;
    }
}

void solve2()
{
    Split_and_Init();
    while (m--)
    {
        int l, r, k;
        scanf("%d %d %d", &l, &r, &k);
        printf("%d\n", Find(l, r, k));
    }
}

int main()
{
    // freopen("flower.in", "r", stdin);
    // freopen("flower.out", "w", stdout);
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
    }

    if (n <= 4000 && m <= 4000)
    {
        solve1();
    }
    else
    {
        solve2();
    }

    return 0;
}

void Calc(int i)
{
    memset(Block[i].Max_Mod, 0, sizeof(Block[i].Max_Mod));
    memset(Block[i].Max_Num_Before, 0, sizeof(Block[i].Max_Num_Before));
    for (int j = Block[i].start; j <= Block[i].end; j++)
    {
        Block[i].Max_Num_Before[a[j]] = a[j];
    }
    for (int j = 1; j <= N; j++)
    {
        Block[i].Max_Num_Before[j] = std::max(Block[i].Max_Num_Before[j], Block[i].Max_Num_Before[j - 1]);
    }

    for (int j = 1; j <= N; j++)
    {
        for (int k = 0; k <= N; k += j) //
        {
            Block[i].Max_Mod[j] = std::max(Block[i].Max_Mod[j], Block[i].Max_Num_Before[std::min(k + j - 1, N)] - k);
        }
    }
}

/*
5 5
1 2 3 4 5
1 3 2
1 3 3
1 4 4
5 5 5
3 5 3

1
2
3
0
2
*/

总结

思路

抽象套路, 伟大的递推
分块 \(Size\) 的套路 : 初始化的时间复杂度 + 查询的时间复杂度

代码

实现代码之前把要用的公式先写好

posted @ 2024-10-07 17:16  Yorg  阅读(6)  评论(0编辑  收藏  举报