[洛谷P3940]分组

题目

思路

好题+细节题

答案字典序要求最小,所以考虑倒叙枚举,对于当前一组需要尽量多的加东西,因为后面组选的数越多,前面的选择机会越多

枚举序列枚举值域,这是这道题的关键

\(K=1\):倒叙枚举到\(i\),此时只需判断当前组中的数是否有加\(a_i\)等于完全平方数的;可以\(O(n)\)枚举,但显然可以更优:枚举所有的完全平方数,对于之前的数开桶记录即可,这样做的复杂度为完全平方数的个数,可以发现最大为512,可以通过这部分数据

\(K=2\):同理倒叙枚举到\(i\),此时分两种情况:

  1. 一般情况下,\(a_i\)第一次出现在这一组中;“如果有与\(a_i\)成为完全平方数的数,那么它们俩不能在同一边”->“两个点不能在同一边”,这是不是很眼熟?就是扩展域并查集好题(裸题关押罪犯,所以直接套用这个做法即可;优化:直接枚举序列仍然是\(O(n^2)\)的,所以令\(f_i\)表示与\(i\)为友的集合,\(f_{i+maxx}\)表示与\(i\)为敌的集合,直接合并并查集就好了

  2. \(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;
}
posted @ 2019-10-21 21:38  擅长平地摔的艾拉酱  阅读(166)  评论(0编辑  收藏  举报
/*取消选中*/