[COCI2019-2020#3] Drvca 题解
先考虑一个直接的思路,我们将 \(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;
}