luogu3940:分组(并查集)

传送门

小 C 在了解了她所需要的信息之后,让兔子们调整到了恰当的位置。小 C 准备给兔子 们分成若干个小组来喂恰当的胡萝卜给兔子们吃。
此时, n 只兔子按一定顺序排成一排,第 i 只兔子的颜色是aii。由于顺序已经是被 调整好了的,所以每个小组都应当是序列上连续的一段。
在分组前,小 C 发现了一个规律:有些兔子会两两发生矛盾。并且,两只兔子会发生矛 盾,当且仅当代表他们的颜色的数值之和为一个正整数的平方比如1色兔子和 2色兔子 不会发生矛盾,因为 3 不是任何一个正整数的平方;而 1 色兔子却会和 3 色兔子发生矛盾,因为 4=22
小 C 认为,只要一个小组内的矛盾不要过大就行。因此,小 C 定义了一个小组的矛盾值 k ,表示在这个小组里,至少需要将这个组再一次分成 k 个小团体;每个小团体并不需 要是序列上连续的一段,但是需要使得每个小团体内任意两只兔子之间都不会发生矛盾。
小 C 要求,矛盾值最大的小组的矛盾值 k 不超过 K 就可以了。当然,这样的分组方 法可能会有很多个;为了使得分组变得更加和谐,小 C 想知道,在保证分组数量最少的情况下,字典序最小的方案是什么。你能帮帮她吗?
字典序最小的方案是指,按顺序排列分组的间隔位置,即所有存在兔子 i i+1在 不同组的位置 ii,和其它所有相同分组组数相同的可行方案相比总有第一个不同的位置比其 它方案小的方案。

题意:
给n个数,要求将其分成m个连续序列。最小化m。
k=1 时序列的条件是其中任意两数相加不能产生平方数。
k=2 时要求序列可以分成两个不同集合,其中任意两数相加不能产生平方数。
要求输出方案(从左往右输出分割的位置),如果有多个输出字典序最小的方案。

题解:
要求输出贪心的方案其实倒着做贪心就好了。现在讨论怎么判定当前序列合法。

对于 k=1 的情况,设现在要加入 ai 到序列,暴力枚举平方数 b ,数组打标记判断bai是否存在。

对于 k=2 的情况变得有些复杂。先说一种 n2 的做法。

不妨把所有的矛盾点对连边,断开这条边就相当于将边两边的数分到不同集合。据题意,不合法的序列中含有奇环,用 Tarjan 可以做到 O(n) 判断。总复杂度为 O(n2)

现在考虑并查集判断合法序列,将每个点拆成两个点 a1,a2 ,若 a1,b2 在一个并查集中说明 a,b 分到了不同的集合。那么连边时判断合法性就好了。

但是有一个问题,这样做严格意义上也是 O(n2) 的,考虑将相同的数合并为1个点(前提是这些值的两倍不是完全平方数),那么可以做到 O(nn) ,而且这个上界完全跑不满,可以过掉这道题。

#include<bits/stdc++.h>
using namespace std;
streambuf *ib,*ob;
inline int read(){
    char ch=ib->sbumpc();int i=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=ib->sbumpc();}
    while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=ib->sbumpc();}
    return i*f;
}
inline void W(int x){
    static int buf[50];
    if(!x){ob->sputc('0');return;}
    if(x<0){ob->sputc('-');x=-x;}
    while(x){buf[++buf[0]]=x%10;x/=10;}
    while(buf[0])ob->sputc(buf[buf[0]--]+'0');
}
const int Maxn=3e5+50;
int n,K,a[Maxn],num[Maxn],tail,tot=0,que[Maxn];
namespace Task1{
    int vis[Maxn];
    inline void solve(){
        int nowcnt=1;
        for(int i=n;i>=1;i--){
            for(int j=tot;j>=1;j--){
                if(num[j]<a[i])break;
                if(vis[num[j]-a[i]]==nowcnt){
                    ++nowcnt;
                    que[++tail]=i;
                    break;
                }
            }
            vis[a[i]]=nowcnt;
        }
        W(nowcnt);ob->sputc('\n');
        for(int i=tail;i>=1;i--){
            W(que[i]);ob->sputc(' ');
        }
    }
}
namespace Task2{
    int anc[Maxn],nowcnt,vis[Maxn],isdouble[Maxn],issquare[Maxn];
    vector<int>o[Maxn];
    inline int getanc(int x){return (anc[x]==x)?x:(anc[x]=getanc(anc[x]));}
    inline bool check(int now){
        for(int i=tot;i>=1;i--){
            if(num[i]<a[now])break;
            int t=num[i]-a[now];
            for(int j=o[t].size()-1;j>=0;j--){
                int v=o[t][j];
                int t1=getanc(now),t2=getanc(v),t3=getanc(now+n),t4=getanc(v+n);
                if(t1==t2||t3==t4)return true;
                anc[t1]=t4;anc[t2]=t3;
            }
        }
        return false;
    }
    inline void solve(){
        for(int i=1;i<=n*2;i++)anc[i]=i;
        for(int i=1;i<=tot;i++)issquare[num[i]]=1;
        for(int i=1;i<=n;i++)if(issquare[a[i]*2])isdouble[a[i]]=1;
        nowcnt=1;int last=n;
        for(int i=n;i>=1;i--){
            if(check(i)){
                ++nowcnt;que[++tail]=i;
                anc[i]=i,anc[i+n]=i+n;
                while(last!=i)o[a[last--]].clear();
            }
            if(vis[a[i]]!=nowcnt)o[a[i]].push_back(i),vis[a[i]]=nowcnt;
            else if(isdouble[a[i]])o[a[i]].push_back(i);
        }
        W(nowcnt);ob->sputc('\n');
        for(int i=tail;i>=1;i--){
            W(que[i]);ob->sputc(' ');
        }
    } 
} 
int main(){
    ios::sync_with_stdio(false);
    cin.tie(NULL);cout.tie(NULL);
    ib=cin.rdbuf();ob=cout.rdbuf();
    n=read(),K=read();
    for(int i=1;i<=n;i++)a[i]=read();
    for(int d=1;d*d<=262200;++d)num[++tot]=d*d;
    if(K==1)Task1::solve();
    else Task2::solve();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值