洛谷3940 分组(贪心)(并查集)

版权声明:本文为博主原创文章,未经博主允许不得转载,除非先点了赞。 https://blog.csdn.net/A_Bright_CH/article/details/83305375

题目

把n个数按顺序分成若干组。定义矛盾为两个数的和为一个完全平方数。
K=1时,不允许组内存在矛盾。
K=2时,把小组分成两个团体,每个团体内不存在矛盾。

特性
很贪心的一个题。
因为要求分组位置尽量靠前,所以从后往前贪心,每次使当前区间尽量长。

题解K=1

模拟
这个很暴力,直接判断i能否加入当前区间即可。

题解K=2

并查集
有点像关押罪犯那题,两个矛盾的尽量分放两边,这个就可以用并查集维护了。每个点拆成两个点,如果有矛盾,就交叉连边。当拆成的两个点处在一个集合中时,矛盾。
此外还要考虑一个数字重复出现的情况。如果要是它们之和不是完全平方数,那么放在一边就可以了;否则就要放两边了,如果放两边,那么就不允许有任何一个数会与他矛盾。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int sqr_inf=131100,N=131100;
const int maxn=131100;

int n,k;
int a[maxn];

int fa[maxn*2];
int findfa(int x)
{
    if(fa[x]==x) return x;
    return fa[x]=findfa(fa[x]);
}
bool check(int x,int y)//判断x和y(已冲突)能不能分放两边 
{
    int x1=findfa(x),x2=findfa(x+N);
    int y1=findfa(y),y2=findfa(y+N);
    if(x1==y1 || x2==y2) return false;
    fa[x1]=y2;fa[x2]=y1;
    return true;//debug
}

bool issqr[maxn*2];
int m=0,b[maxn];
bool vis[maxn],dvis[maxn];
void work1()
{
    for(int i=n,j=n;i>=1;)//debug i--
    {
        for(;j>=1;j--)
        {
            for(int k=1;k*k-a[j]<sqr_inf;k++) if(k*k-a[j]>0)
                if(vis[k*k-a[j]]) goto fail;
            vis[a[j]]=true;
        }
fail:    if(j==0) break;
        b[++m]=j;
        for(;i>j;i--) vis[a[i]]=false;
    }
}
void work2()
{
    for(int i=1;i<=2*N;i++) fa[i]=i;
    for(int i=1;i*i<2*sqr_inf;i++) issqr[i*i]=true;//debug <sqr_inf 因为当a[j]=N时,最大有N*2 
    for(int i=n,j=n;i>=1;)//debug i--
    {
        for(;j>=1;j--)
            if(vis[a[j]])//a[j]出现过,现在是第2次出现 
            {
                if(issqr[a[j]*2])
                {
                    if(dvis[a[j]]) goto fail;//已经出现了两次 
                    for(int k=1;k*k-a[j]<sqr_inf;k++) if (k*k-a[j]>0)
                        if(vis[k*k-a[j]] && k*k!=a[j]*2) goto fail;//两边都有一个a[j],如果有一个数为k*k-a[j],放那边都不行 
                    dvis[a[j]]=true;//重复出现 
                }
            }
            else//第1次出现 
            {
                for(int k=1;k*k-a[j]<sqr_inf;k++) if(k*k-a[j]>0)
                    if(vis[k*k-a[j]] && ( dvis[a[j]] || dvis[k*k-a[j]] || !check(a[j],k*k-a[j]) )) goto fail;//如果不能放两边,而且各放一边还会冲突,那不行 
                vis[a[j]]=true;//出现记录 
            }
fail:    if(j==0) break;
        b[++m]=j;
        for(;i>j;i--) fa[a[i]]=a[i],fa[a[i]+N]=a[i]+N,vis[a[i]]=dvis[a[i]]=false;
        fa[a[i]]=a[i];fa[a[i]+N]=a[i]+N;//注意i无法还原到i=j 
    }
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    if(k==1) work1();
    else work2();
    printf("%d\n",m+1);
    for(int i=m;i>=1;i--) printf("%d ",b[i]);
    puts("");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/A_Bright_CH/article/details/83305375
今日推荐