CF1699E

CF1699E Three Days Grace

题面翻译

给定一个初始有 \(n\) 个元素的可重复集合 \(A\),其中每个元素都在 \(1\)\(m\) 之间。

每次操作可以将 \(A\) 中的一个元素(称之为 \(x\))从 \(A\) 中删除,然后在 \(A\) 中加入两个元素 \(p,q\),满足 \(p\cdot q=x\)\(p,q>1\)

显然每次操作后 \(A\) 的大小会增加 \(1\)

定义 \(A\) 的平衡值为 \(A\) 中的最大值减去最小值,求任意次操作(可以是 \(0\) 次)后最小可能的平衡值。

【输入格式】

第一行输入一个整数 \(T\)\(1\leq T\leq 10^5\)),表示测试数据组数。

每个测试数据包含两行。

对于每个测试数据:

第一行包含两个整数 \(n\)\(1\leq n\leq 10^6\)),\(m\)\(1\leq m\leq 5\cdot 10^6\)),分别表示数组 \(A\) 的元素个数以及 \(A\) 中元素的最大可能值。

保证所有测试数据中的 \(T\)\(n\) 之和不超过 \(10^6\),所有测试数据中的 \(T\)\(m\) 之和不超过 \(5\cdot 10^6\)

第二行包含 \(n\) 个整数 \(a_1,a_2,\ldots ,a_n\)\(1\leq a_i\leq 5\cdot 10^6\))。

【输出格式】

对于每个测试数据,输出一行一个整数,表示最小可能平衡值。

题目描述

Ibti was thinking about a good title for this problem that would fit the round theme (numerus ternarium). He immediately thought about the third derivative, but that was pretty lame so he decided to include the best band in the world — Three Days Grace.

You are given a multiset $ A $ with initial size $ n $ , whose elements are integers between $ 1 $ and $ m $ . In one operation, do the following:

  • select a value $ x $ from the multiset $ A $ , then
  • select two integers $ p $ and $ q $ such that $ p, q > 1 $ and $ p \cdot q = x $ . Insert $ p $ and $ q $ to $ A $ , delete $ x $ from $ A $ .

Note that the size of the multiset $ A $ increases by $ 1 $ after each operation.

We define the balance of the multiset $ A $ as $ \max(a_i) - \min(a_i) $ . Find the minimum possible balance after performing any number (possible zero) of operations.

输入格式

The first line of the input contains a single integer $ t $ ( $ 1 \le t \le 10^5 $ ) — the number of test cases.

The second line of each test case contains two integers $ n $ and $ m $ ( $ 1 \le n \le 10^6 $ , $ 1 \le m \le 5 \cdot 10^6 $ ) — the initial size of the multiset, and the maximum value of an element.

The third line of each test case contains $ n $ integers $ a_1, a_2, \ldots, a_n $ ( $ 1 \le a_i \le m $ ) — the elements in the initial multiset.

It is guaranteed that the sum of $ n $ across all test cases does not exceed $ 10^6 $ and the sum of $ m $ across all test cases does not exceed $ 5 \cdot 10^6 $ .

输出格式

For each test case, print a single integer — the minimum possible balance.

样例 #1

样例输入 #1

4
5 10
2 4 2 4 2
3 50
12 2 3
2 40
6 35
2 5
1 5

样例输出 #1

0
1
2
4

提示

In the first test case, we can apply the operation on each of the $ 4 $ s with $ (p,q) = (2,2) $ and make the multiset $ {2,2,2,2,2,2,2} $ with balance $ \max({2,2,2,2,2,2,2}) - \min({2,2,2,2,2,2,2}) = 0 $ . It is obvious we cannot make this balance less than $ 0 $ .

In the second test case, we can apply an operation on $ 12 $ with $ (p,q) = (3,4) $ . After this our multiset will be $ {3,4,2,3} $ . We can make one more operation on $ 4 $ with $ (p,q) = (2,2) $ , making the multiset $ {3,2,2,2,3} $ with balance equal to $ 1 $ .

In the third test case, we can apply an operation on $ 35 $ with $ (p,q) = (5,7) $ . The final multiset is $ {6,5,7} $ and has a balance equal to $ 7-5 = 2 $ .

In the forth test case, we cannot apply any operation, so the balance is $ 5 - 1 = 4 $ .

最难的部分应该是想到dp吧。
确实太没有启发性。我能够想到的思路就是,寻找极差,那便尝试维护一个双指针,先设定下限,然后对着这个下限尝试分解看看上线最低多少。
但是想到这个还不行,还需要注意到\(m\leq5e6\),我们不应该对于整个数组,而应该是对于每个确定的数字来计算。如果能想到这一点,其实dp就好像了。
但是上面这两个要结合起来想到,不然都出不了这个做法。所以有一定的难度。

\(f[i][j]\)表示对于数字\(j\),分解出的最小的数字大于等于\(i\)的情况下最大的数字最小是多少。
考虑转移。

如果\(i\nmid j\),那么\(f[i][j]=f[i+1][j]\),因为不可能分解出\(i\)这个数字,也就不会有影响。

如果\(i\mid j\),那么就尝试去拆一个\(i\)出来,剩下的数字就是\(j/i\),那么只需要让\(f[i][j*i]=\min(f[i][j*i],f[i][j])\)即可。同时要注意\(j/i\geq i\),也就是\(i^2\leq j\)

其实这个时候就很容易发现,滚动数组直接可以优化。这样第一维就消失了,空间变得可以接受。
再分析一下时间复杂度,可以发现,对于当前枚举的数字\(i\),我们只需要枚举所有\(i\)的倍数的位置进行转移即可。
也就是总体是\(O(m\ln m)\)的复杂度。
但是这个并没有真正求出我们需要的答案。我们真正所需要的答案还需要处理。

对于所有的\(j\in a\),我们需要枚举所有的\(i\)来尝试找到答案的最优值。数据结构是可以做到的,每次dp值发生变化就进行一次修改,单点修改,查询整个区间最大值。
这样可以做,但是其实不用这么麻烦。可以发现,随着我们dp的进行,相同位置的\(dp[j]\)的值是单调不升的。所以直接用桶维护即可,可以使用一个指针指向最大值,如果没有就下降指针。这样可以证明是正确的。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read()
{
    char c=getchar();ll a=0,b=1;
    for(;c<'0'||c>'9';c=getchar())if(c=='-')b*=-1;
    for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
    return a*b;
}
ll n,m,a[5000001];
ll f[5000001];//f[i][j]表示把数字j分解后,保证最小的数字大于i的情况下最大的数字是多少
ll tong[5000001],flag[5000001];
int main()
{
    ll T=read();
    while(T--)
    {
        n=read(),m=read();
        for(ll i=1;i<=m;i++)flag[i]=0,tong[i]=0;
        ll Min=m;
        for(ll i=1;i<=n;i++)
        {
            a[i]=read();
            Min=min(Min,a[i]);
            flag[a[i]]=1;
        }
        for(ll i=1;i<=m;i++)
        {
            f[i]=i;
            if(flag[i])tong[i]++;
        }
        ll Max=m,ans=m;
        for(ll i=m;i>=1;i--)
        {
            for(ll j=i;1LL*j*i<=m;j++)
            {
                if(flag[j*i]==1)tong[f[j*i]]--;
                f[j*i]=min(f[j*i],f[j]);
                if(flag[j*i]==1)tong[f[j*i]]++;
            }
            while(tong[Max]==0)Max--;
            if(i<=Min)ans=min(ans,Max-i);
        }
        cout<<ans<<endl;
    }
    return 0;
}

代码很简洁,但是这个题目绝对是我最近做的最好的题目。

首先是题目的描述非常的自然,简洁而巧妙。
直接看是完全看不出来dp的形式的,只有经过一定的分析才能想到用dp。其中正如上面说的,想到双指针可能可以做出来。但是同时还要想到\(a\leq 5e6\)真正的作用。这部分我个人觉得是非常非常难的,也是这题2600的难度所在。

这题为我提供了两个新的理解。
第一个是这个巧妙的dp。拆分问题,同时这dp的形式应该是我第一次见。
第二个是dp得到的答案的单调性,可以用桶这种办法来维护。

初见能过这题的感觉都是神人了。小鸡还是太强了。

posted @ 2024-11-23 15:08  HL_ZZP  阅读(2)  评论(0编辑  收藏  举报