题目
思路
好题+细节题
答案字典序要求最小,所以考虑倒叙枚举,对于当前一组需要尽量多的加东西,因为后面组选的数越多,前面的选择机会越多
化枚举序列为枚举值域,这是这道题的关键
\(K=1\):倒叙枚举到\(i\),此时只需判断当前组中的数是否有加\(a_i\)等于完全平方数的;可以\(O(n)\)枚举,但显然可以更优:枚举所有的完全平方数,对于之前的数开桶记录即可,这样做的复杂度为完全平方数的个数,可以发现最大为512,可以通过这部分数据
\(K=2\):同理倒叙枚举到\(i\),此时分两种情况:
一般情况下,\(a_i\)第一次出现在这一组中;“如果有与\(a_i\)成为完全平方数的数,那么它们俩不能在同一边”->“两个点不能在同一边”,这是不是很眼熟?就是扩展域并查集好题(
裸题)关押罪犯,所以直接套用这个做法即可;优化:直接枚举序列仍然是\(O(n^2)\)的,所以令\(f_i\)表示与值\(i\)为友的集合,\(f_{i+maxx}\)表示与值\(i\)为敌的集合,直接合并并查集就好了\(a_i\)多次出现且\(2\times a_i\)为完全平方数,上面的值域并查集并不能很好的处理这种自己和自己的关系,所以需要特判
细节:上面的1无法考虑到另一个数自己和自己成完全平方的情况,需要特殊处理,在代码中有注释(如果没有考虑到会错后面几个点)
Code
#include<bits/stdc++.h>
#define N 150005
#define Min(x,y) ((x)<(y)?(x):(y))
#define Max(x,y) ((x)>(y)?(x):(y))
#define re register
using namespace std;
typedef long long ll;
int temp=512,c=512*512+1;
int n,k,a[N],ct[N],tot;
bool exist[N<<2];
template <class T>
void read(T &x)
{
char c; int sign=1;
while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
bool check1(int x)
{
for(re int i=temp;i>=1;--i)
{
int c=i*i-x;
if(c<=0) break;//颜色默认为正数?
if(exist[c]) return 0;
}
return 1;
}
void solve1()
{
int l=n;
for(int i=n;i>=1;--i)
{
if(check1(a[i])) exist[a[i]]=1;
else//i为新的开始
{
for(int j=l;j>i;--j) exist[a[j]]=0;
//清零当然不能么么set啦qwq
exist[a[i]]=1;
ct[++tot]=i;
l=i;
}
}
printf("%d\n",tot+1);
for(int i=tot;i>=1;--i) printf("%d ",ct[i]);
printf("\n");
}
int fa[N<<4],vis[N<<4];
bool tag[N<<4];
int find(int x) {return x==fa[x] ? x : fa[x]=find(fa[x]);}
int merge(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx==fy) return 0;
fa[fx]=fy;
return 1;
}
void solve2()
{
int l=n;
for(int i=1;i*i<=c*2;i++) tag[i*i]=1;
for(int i=1,t=(c<<1);i<=t;++i) fa[i]=i;
for(int i=n;i>=1;--i)
{
bool no=0;
if(vis[a[i]])//重复出现
{
if(tag[2*a[i]])//分在不同集合,否则一个集合不管
{
if(vis[a[i]]==2) no=1;
//自己已有两个
else for(int j=temp;j>=1;--j)
{
if(a[i]>j*j) break;
if((vis[j*j-a[i]]&&j*j!=a[i]*2)) { no=1; break; }
}
}
}
else for(int j=temp;j>=1;--j)
{
if(a[i]>j*j) break;
if(vis[j*j-a[i]])
{
if(tag[2*(j*j-a[i])]&&vis[j*j-a[i]]==2) no=1;
//如果它出现了两次 且 在两边,直接判负
//下面的操作无法判断上面这种情况
if(find(a[i])==find(j*j-a[i])) no=1;
merge(a[i]+c,j*j-a[i]);
merge(j*j-a[i]+c,a[i]);
}
if(no) break;
}
if(no)
{
for(int j=i;j<=l;++j) vis[a[j]]=0,fa[a[j]]=a[j],fa[a[j]+c]=a[j]+c;
ct[++tot]=i;
l=i;
}
vis[a[i]]++;
}
printf("%d\n",tot+1);
for(int i=tot;i>=1;--i) printf("%d ",ct[i]);
printf("\n");
}
int main()
{
freopen("division.in","r",stdin);
freopen("division.out","w",stdout);
read(n);read(k);
for(int i=1;i<=n;++i) read(a[i]);
if(k==1) solve1();
else solve2();
return 0;
}