CF1699E

HL_ZZP發表於2024-11-23

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得到的答案的單調性,可以用桶這種辦法來維護。

初見能過這題的感覺都是神人了。小雞還是太強了。