有些夢想雖然遙不可及,但不是不可能實現。只要我足夠的強。
前言
調了挺長時間的,並查集合並的時候需要 find 一下,不然會炸記憶體。。。。
解題思路
參考了題解區一篇思路非常好的題解,在這裡講一下自己的見解。
首先明確一下 K 的取值只有 1 或者 2 這裡看資料範圍非常重要!,對於 \(K=1\),\(K=2\) 的情況要分開來做。
K=1
對於 \(K=1\) 的情況,為了保證字典序最小,我們需要倒著列舉序列了。
然後再次觀察資料範圍,發現\(131072 \times2=512^2\),因此我們可以列舉 \(1 \sim 512\) ,令 vis[i] 表示在當前掃到的組裡顏色為 i 的是否存在,檢視是否訪問過 \(x^2-s_i\) 。
-
如果訪問過,表示和第 i 只兔子發生矛盾的已經在這個組裡了,因此需要再次分一個組,並且記錄下分組的邊界,清空 vis 陣列。
-
如果沒有訪問過,把該種顏色的標記成 true 記錄就好了。
K=2
幾乎同樣的思路,我們仍然需要倒著列舉序列。
對於同一組的兔子,狀態之可能有兩種:同一小團體或者在敵對小團體,因此我們用並查集維護。
-
\(\operatorname{find}(1 \sim n)\) 表示 \(1\sim n\)的兔子所在的小團體。
-
\(\operatorname{find}(n+1 \sim 2 \times n)\) 表示 \(1\sim n\)的兔子所在的小團體的敵對小團體。
然後開一個 vector 陣列記錄同一顏色的序號,然後分別對於發生矛盾的兔子進行判斷,同時更新該兔子所在組以及小團體和敵對小團體。
同樣的,如果矛盾無法避免,那就重新開一個組,並清空標記,記錄分割點就好了。
code
#include<bits/stdc++.h>
using namespace std;
const int M=131080;
int n,m,K,las,s[M],fa[M<<1];
vector<int> ans,v[M<<1];
bool vis[M];
int find(int x)
{
if(fa[x]==x)
return x;
return fa[x]=find(fa[x]);
}
void work_1()
{
for(int i=n;i>=1;i--)
{
bool flag=true;
for(int j=1;j<=512;j++)
if(j*j>=s[i])
if(vis[j*j-s[i]])
{
flag=false;
break;
}
if(!flag)
{
for(int j=i+1;j<las;j++)
vis[s[j]]=false;
ans.push_back(i);
las=i+1;
}
vis[s[i]]=true;
}
printf("%d\n",ans.size()+1);
for(int i=ans.size()-1;i>=0;i--)
printf("%d ",ans[i]);
}
int update(int l,int r)
{
for(int i=l+1;i<r;i++)
vector <int>().swap(v[s[i]]);
ans.push_back(l);
return l+1;
}
void work_2()
{
for(int i=1;i<=(n<<1);i++)
fa[i]=i;
for(int i=n;i>=1;i--)
{
for(int j=1;j<=512;j++)
if(j*j>=s[i])
if(v[j*j-s[i]].size())
for(int k=0;k<v[j*j-s[i]].size();k++)
{
int temp=v[j*j-s[i]][k];
if(find(temp)==find(i))
{
las=update(i,las);
break;
}
else
{
fa[find(i+n)]=find(temp);
fa[find(temp+n)]=find(i);
}
}
v[s[i]].push_back(i);
}
printf("%d\n",ans.size()+1);
for(int i=ans.size()-1;i>=0;i--)
printf("%d ",ans[i]);
}
int main()
{
scanf("%d%d",&n,&K);
las=n+1;
for(int i=1;i<=n;i++)
scanf("%d",&s[i]);
if(K==1)
work_1();
else
work_2();
return 0;
}