[洛谷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;
}

猜你喜欢

转载自www.cnblogs.com/Chtholly/p/11716416.html
今日推荐