[COCI2019-2020#3] Drvca 題解

Supor__Shoop發表於2024-09-27

先考慮一個直接的思路,我們將 \(a\) 從小到大排序,然後在其中選擇一個等差子序列,接著判斷 \(a\) 中剩下的數是否滿足等差性質。

如果我們要高效率,那麼肯定不能列舉這個子序列。考慮找一些有趣的性質:對於排序後的 \(a_1,a_2,a_3\),由於我們需要找到兩個不交叉子序列,並且每個數都必須被選擇一次,根據抽屜原理,我們可以知道 \(a_1,a_2,a_3\) 這三個數中必有至少兩個數是在同一個子序列中的

找到了這個性質,我們想一想怎麼利用。不難發現一個等差序列 \(b_i\),如果序列長度已知,那麼只需要確定 \(b_1\) 和公差就可以直到整個序列了。於是我們考慮在 \(a_1,a_2,a_3\) 中任意選擇 \(2\) 個數 \(a_x,a_y\)\(1\leq x<y\leq 3\)),然後假定 \(a_x\) 為起點,\(a_y-a_x\) 為公差 \(d\)。接著我們用一個棧,初始時先把 \(a_x,a_y\) 放進去,然後定義一個變數 \(k\leftarrow a_y+d\),對於當前的 \(k\),我們在 \(a\) 中尋找是否存在這個值,如果存在就放入棧,\(k\leftarrow k+d\)

為了計算答案,每次得到一個新的棧時,我們假定此時棧中組成的序列就是其中一個等差子序列,於是我們就要判斷剩下的元素是否等差。

令我們已選出來的子序列的最後一項是 \(a_p\),我們將剩下的元素分為下標 \(<p\) 和下標 \(>p\) 的,那麼不合法情況就是四種:前者不等差,後者不等差,前後都等差但是兩者公差不同,前後公差相同但是與前後交界處的兩個數的差不同。

先判斷前後者是否等差:對於後者我們發現這些元素都是 \(a\) 的一段字尾,於是我們考慮預處理 \(t_i\) 表示 \([i,n]\) 元素是否等差,如果不是則 \(t_i=-1\),如果是則 \(t_i\) 為這個公差。假如 \(t_{p+1}=-1\),那麼答案肯定不行。而對於前者,我們在將元素放入棧時,依次把前面未被使用的元素放入另一個棧裡面,這樣時間複雜度就是均攤 \(O(n)\) 的,隨時判斷一下即可。

然後判斷後面兩種特殊情況:我們對於下標 \(<p\) 的未使用元素也可以計算一個公差 \(d\prime\),如果 \(d\prime\not= t_{p+1}\),則顯然不行。然後對於交界處的兩個數,一個是 \(a_{p+1}\),一個是第二個棧裡的最後一個元素,我們將其相減判斷一下即可。

注意到一些特殊情況,比如前後兩部分的元素個數為 \(0,1\),這個時候它們分別是不存在公差的,這裡我們特判一下就行了。

但是題目並沒有告訴我們 \(a_i\) 是互不相同的,因此會出現兩個數相同的情況。當 \(a_1,a_2,a_3\) 中出現了相同的數,並且當前我們欽定的公差 \(d=0\) 時,為了防止死迴圈,我們就再特判一下,即將相同的數全部拎出來。但是如果 \(a_i\) 兩兩相同,我們就考慮拆成兩個部分,因為題目要求我們保證兩個子序列不為空。

總的來說就是細節比較多,如果調不動了可以參考以下程式碼:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,a[MAXN];
unordered_map<int,int> mp;
int stk[MAXN],flag[MAXN];
vector<int> res1;
void solve(int s,int d)
{
    if(!d)
    {
        int cnt=0;
        for(int i=1;i<=n;i++)
        {
            if(a[i]!=a[s])  stk[++cnt]=a[i];
        }
        int flag=0;
        for(int i=3;i<=cnt;i++)   flag|=(stk[i]-stk[i-1]!=stk[2]-stk[1]);
        if(flag||!cnt&&n<2)    return;
        if(!cnt)    stk[++cnt]=a[1];
        cout<<cnt<<'\n';
        for(int i=1;i<=cnt;i++) cout<<stk[i]<<" ";
        cout<<'\n'<<n-cnt<<'\n';
        for(int i=1;i<=n-cnt;i++)   cout<<a[s]<<" ";
        exit(0);
    }
    res1.clear();
    int val=a[s],cnt=0,lst=0,t;
    while(t=mp[val])
    {
        res1.push_back(val);
        int p=cnt;
        for(int i=lst+1;i<t;i++)    stk[++cnt]=a[i];
        for(int i=p+1;i<=cnt;i++)
        {
            if(i>2&&stk[i]-stk[i-1]!=stk[i-1]-stk[i-2]) return;
        }
        if(!(t<n&&(flag[t+1]<0||cnt>1&&t<n-1&&flag[t+1]!=stk[2]-stk[1]||cnt>1&&a[t+1]-stk[cnt]!=stk[2]-stk[1]||t<n-1&&cnt&&flag[t+1]!=a[t+1]-stk[cnt])))
        {
            int len=res1.size();
            if(len==n)  len--,stk[++cnt]=a[n];
            cout<<len<<'\n';
            for(int i=0;i<len;i++)  cout<<res1[i]<<" ";
            cout<<'\n';
            cout<<n-len<<'\n';
            for(int i=1;i<=cnt;i++) cout<<stk[i]<<" ";
            for(int i=t+1;i<=n;i++) cout<<a[i]<<" ";
            exit(0);
        }
        lst=t,val+=d;
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)   cin>>a[i];
    sort(a+1,a+n+1);
    for(int i=1;i<=n;i++)   mp[a[i]]=i;
    if(n==2)    return (cout<<"1\n"<<a[1]<<'\n'<<"1\n"<<a[2]),0;
    flag[n-1]=a[n]-a[n-1];
    for(int i=n-2;i>=1;i--)
    {
        if(~flag[i+1]&&a[i+1]-a[i]==a[i+2]-a[i+1])    flag[i]=flag[i+1];
        else    flag[i]=-1;
    }
    solve(1,a[2]-a[1]),solve(1,a[3]-a[1]),solve(2,a[3]-a[2]);
    cout<<"-1";
    return 0;
}