【题解】洛谷 P3940 划分【20201014 CSP 模拟赛】【贪心 带权并查集】

题目链接

题目链接

题意

将序列 \(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;
}

posted @ 2020-10-14 09:46  破壁人五号  阅读(107)  评论(0编辑  收藏  举报