题意
将序列 \(s\) 分为尽可能少、断点字典序尽可能小的段,要求每一段都能通过被分为 \(k\) 个集合,使每个集合内部没有两个数之和为完全平方数。\(n,s_i\leq 2^{18}\),\(k\in \{1,2\}\)。
题解
从后往前贪心显然最优,只需要判断一个段再加一个数之后是否仍能分成 \(k\) 个集合即可。\(k=1\) 是 trivial 的(用桶之类的维护即可),\(k=2\) 时就是判断是否是二分图,这个拿带权并查集维护即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll getint(){
ll ans=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
ans=ans*10+c-'0';
c=getchar();
}
return ans*f;
}
const int N=1.5e5+10;
int n,k,a[N];
int v;
namespace subt_k1{
int b[N];
}
namespace subt_k2{
vector<int>b[N];
int f[N],d[N];
int _(int x){
if(x==f[x])return x;
int t=_(f[x]);
d[x]+=d[f[x]];
f[x]=t;
return f[x];
}
int merge(int x,int y){
int xx=_(x),yy=_(y);
f[yy]=xx;
d[yy]=d[x]+d[y]+1;
}
}
vector<int>ans;
int main(){
n=getint(),k=getint();
for(int i=1;i<=n;i++)v=max(v,a[i]=getint());
if(k==1){
using namespace subt_k1;
int r=n;
for(int i=n;i>=1;--i){
bool ok=1;
for(int j=1;j*j-a[i]<=v;j++){
if(j*j-a[i]>=0&&b[j*j-a[i]]){
ok=0;
break;
}
}
if(!ok){
ans.push_back(i);
for(int j=i+1;j<=r;j++)b[a[j]]=0;
r=i;
}
b[a[i]]=1;
}
}else{
using namespace subt_k2;
for(int i=1;i<=n;i++)f[i]=i;
int r=n;
for(int i=n;i>=1;--i){
bool ok=1;
if(b[a[i]].size()>2)continue;
for(int j=1;j*j-a[i]<=v;j++){
if(j*j-a[i]>=0){
for(auto x:b[j*j-a[i]]){
int y=i;
int xx=_(x),yy=_(y);
if(xx!=yy){
merge(x,y);
continue;
}
if((d[x]+d[y])%2)continue;
ok=0;
break;
}
}
}
if(!ok){
ans.push_back(i);
for(int j=i;j<=r;j++)b[a[j]].clear(),d[j]=0,f[j]=j;
r=i;
}
b[a[i]].push_back(i);
}
}
printf("%d\n",ans.size()+1);
for(int i=ans.size()-1;i>=0;i--){
printf("%d ",ans[i]);
}
return 0;
}