分组 (并查集)

分组

10.23

问题可以转化为,从后往前,选择一段最长的合法区间并分割,重复进行直到完成为止。
这里写图片描述这里写图片描述这里写图片描述

从std中收获一种神奇的并查集写法(下面隐藏处),准备研究研究。(按秩合并优化路径压缩??还只有一个数组??看起来就很优)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define N 150000
#define LL long long
using namespace std;

inline int read(){
    int x = 0, f = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); }
    while(ch >= '0' && ch <= '9'){ x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

int n, idc = 0, K;
int a[N], ans[N];
bool vis[N], dvis[N], issqr[N * 2];
int f[N * 2];

int getfa(int x) {
    //return f[x] > 0 ? (f[x] = getfa(f[x])) : x;
    return f[x] == x ? x : (f[x] = getfa(f[x]));
}

void unionn(int u, int v) {
    u = getfa(u), v = getfa(v);
    if (u != v) {
        //if (f[u] > f[v]) swap(u, v);
        //f[u] += f[v];
        f[v] = u;
    }
}

bool check(int u, int v) {
    int s1 = getfa(u), s2 = getfa(u + N);//反点 
    int t1 = getfa(v), t2 = getfa(v + N);
    if (s1 == t1) return 1;
    if (s2 == t2) return 1;
    unionn(s1, t2); unionn(s2, t1);
    return 0;
}

void solve1() {
    for (int i=n, j=n; i;) {
        for (bool flag=1; j; j--) {
            for (int k=1; k*k-a[j] < N; k++) {
                if (k*k-a[j] <= 0) continue;
                if ( vis[k*k-a[j]] ) { flag = 0; break;} 
            }
            if ( !flag ) break;
            vis[a[j]] = 1;
        }
        if ( !j ) break;
        ans[++idc] = j;
        for ( ; i>j; i--) vis[a[i]] = 0;
    }
}

void solve2() {
    //memset(f, -1, sizeof f);
    for(int i=1; i<2*N; i++) f[i] = i;
    for(int i=1; i*i<2*N; i++) issqr[i*i] = 1;
    for(int i=n, j=n; i; ) {
        for( ; j; j--) {
            bool flag = 1;
            if ( vis[a[j]] ) {
                if ( issqr[a[j] + a[j]] ) {//当前堆内冲突 
                    if ( dvis[a[j]] ) break;//区间内已有两个相同数字 
                    for (int k=1; k*k-a[j]<N; k++) {
                        if (k*k-a[j] <= 0) continue;
                        if (vis[k*k-a[j]] && k*k != a[j]*2) {
                            flag = 0; break;
                        }
                    }
                    if ( !flag ) break;
                    dvis[a[j]] = 1;
                }
            }
            else {//之前没有出现过 
                for (int k=1; k*k-a[j] < N; k++) {
                    if (k*k-a[j] <= 0) continue;
                    if ( vis[k*k-a[j]] ) {//处理并查集 
                        if ( dvis[k*k-a[j]] || dvis[a[j]] ) { flag = 0; break;} 
                        if ( check(k*k-a[j], a[j]) ) {flag = 0; break;}
                    } 
                }
                if ( !flag ) break;
                vis[a[j]] = 1;
            }
        }
        if ( !j ) break;
        ans[++idc] = j;
        //for ( ; i>j; i--) f[a[i]] = f[a[i] + N] = -1, vis[a[i]] = 0, dvis[a[i]] = 0;
        for ( ; i>j; i--) f[a[i]] = a[i], f[a[i] + N] = a[i] + N, vis[a[i]] = 0, dvis[a[i]] = 0;
    }
}

int main() {
    freopen("division.in", "r", stdin);
    freopen("division.out", "w", stdout);
    scanf("%d%d", &n, &K);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    if (K == 1) solve1();
    else solve2();
    printf("%d\n", idc + 1);
    for(int i=idc; i; i--) printf("%d ", ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值