P3940 分组(贪心+并查集)

7 篇文章 0 订阅
3 篇文章 0 订阅

因为字典序最小。

组越少 + 越前面的组size越小!

考虑数据范围131072!好巧啊!131072 + 131072 = 262144 = 512 * 512.也就是说任意两种颜色之和小于等于512的平方。。。。

那么我们对于一个元素i可以从512倒着枚举z(这样可以剪掉平方比i小的,省时),表示它和另一个元素j之和是z的平方。

对于k == 1;

因为组内元素,必须有序(每个小组都是序列上连续的一段)

倒着贪心,如果当前元素i能放进上个组(上个组没有和它之和为平方数)则放.不能放新开一个组,

不能放的话,当前元素则是两个组的分界线。

怎么找有没有和元素i之和为平方数的数?

从512倒着枚举,假设枚举的数为z ,如果z*z - i在上一组就必须新开一个组。

k == 2;

也是倒着贪心,只不过当前元素能不能放进上个组的条件变了。

不能出现当前元素和上个组里的元素i矛盾,同时也和上个组里的元素j矛盾,而i和j也矛盾这种情况。出现则必须新开组。

//当前元素和元素i不能在同一个小团体,前元素和元素j不能在同一个小团体,元素j和元素i不能在同一个小团体,一个组最多2个小团体。

怎么表示这种矛盾关系呢??是不是和noip关押罪犯很像啊。用并查集表示敌对关系。

如果当前元素和元素i矛盾,就把当前元素的位置和i的位置+n,并在一起(i 的位置+ n表示i的敌人所在的位置(当然是虚构的,所以每个位值,其实需要两个位置,另一个表示它的敌人所在位置),这样表示i的敌人和当前元素在同一个小团体).

如果当前元素和元素i已经有共同的祖先了,显然当前元素必须在新开的组里面。

当然每组的元素可能重复,vector[i]记录元素i所对应的出现的所有位置。

#include<bits/stdc++.h>
using namespace std;
vector<int>he[300000];
int n,k,a[150000],an[159999],fa[300000], vis[159999],ans = 1;
void read(int &x)
{
	x = 0;  int f = 0;  char c = getchar();
	 while(c < '0' || c > '9')
	 {
	 	if(c == '-')  f = 1;    c = getchar();
	 }
	 while(c >= '0' && c <= '9')
	 {
	 	x = x * 10 + c - '0';   c = getchar();
	 }
	 if(f) x = -x;
}
int find(int x)
{
	if(fa[x] == x) return x;
	return fa[x] = find(fa[x]);
}
void unio(int x,int y)
{
	int r1 = find(x); int r2 = find(y);
	fa[r1] = r2;
}
void solve1()
{
	for(int i = n; i >= 1; i--)
	{
		int ha = 0;
		for(int j = 512; j >= 1; j--)
		{
			if(j*j < a[i]) break;
			if(vis[j * j - a[i]] == ans)   {ha = 1; break;}
		}
		if(ha) an[++ans] = i;
		vis[a[i]]= ans;
	} 
	printf("%d\n",ans);
	for(int i = ans; i > 1; i--)
	printf("%d ",an[i]); 
}
void solve2()
{
 for(int i = 2*n; i >= 1; i--) fa[i] = i;
    for(int i = n; i >= 1; i--)
    {
    	int ha = 0;
    	for(int j = 512; j >= 1 && ha == 0; j--)
    	{
    		if(j*j < a[i]) break;
    		if(vis[j * j - a[i]] == ans)
    			for(int k = 0; k < he[j * j -a[i]].size(); k++)
    			{
    				if(find(i) == find(he[j * j -a[i]][k])) 
					{ha = 1;break;}
                        unio(i+n,he[j * j -a[i]][k]);unio(i,he[j * j -a[i]][k]+n);     
				}
		}
		if(ha)   {  an[++ans] = i;	  }
		if(vis[a[i]] != ans) {
		vis[a[i]] = ans;	 he[a[i]].clear();
		}
		he[a[i]].push_back(i);
	}
		printf("%d\n",ans);
	for(int i = ans; i > 1; i--)
	printf("%d ",an[i]); 
}
int main()
{
	read(n);read(k);
	for(int i = 1; i <= n ;i ++)   read(a[i]);
     if(k == 1)solve1();
     else solve2();
     return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值