CSP-S模拟1 [斐波那契,数颜色,分组]

CSP-S 模拟1

洛谷上原题,不挂题面了。

A.斐波那契

P3938 斐波那契

image

观察上图,可发现规律:一个数的父亲等于这个数减去最大的小于它的斐波那契数。特殊的,如果这个数是斐波那契数,设这个数为 \(x\)\(x = fib(i)\),那它的父亲为 \(fib(i - 2)\)

数据最大到 \(1e12\),打表发现,\(fib(60) > 1e12\)。所以预处理出来 \(fib(1) ... fib(60)\),再二分查找。

找两个点的最近公共祖先,用类似树剖的思想,一个个的往上跳。这棵树的最大深度是 \(60\),查到根总计 \(60\),均摊为 \(O(1)\)。我们把找父亲的复杂度记作 \(O(findfatℎer)\)
总复杂度:\(O(30m×O(findfatℎer))\)
最优复杂度:\(O(60m)\)

Code

#include<cstdio>
#include<algorithm>

#define LL long long

using namespace std;

const int MAXM = 3e5 + 20, SIZE = 65;
int m;
LL fib[SIZE];

inline LL read(){
    LL x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

void init(){
    fib[1] = 1, fib[2] = 1;
    for(register int i = 3; i <= 60; i++)
        fib[i] = fib[i - 1] + fib[i - 2];
}

int Find_Pos(LL num){
    int l = 1, r = 60, ans = 0;

    while(l <= r){
        int mid = (l + r) >> 1;

        if(fib[mid] <= num){
            ans = mid;
            l = mid + 1;
        }
        else r = mid - 1;
    }

    return ans;
}

LL Get_Lca(LL a, LL b){
    int pos_a, pos_b;

    while(a != b){
        pos_a = Find_Pos(a), pos_b = Find_Pos(b);

        if(pos_a < pos_b) swap(a, b), swap(pos_a, pos_b);
        a -= fib[pos_a];

        if(!a) a = fib[pos_a - 2];
    }

    return a;
}

int main(){
    init();

    m = read();
    for(register int i = 1; i <= m; i++){
        LL a, b;
        a = read(), b = read();
        
        printf("%lld\n", Get_Lca(a, b));
    }

    return 0;
}

B.数颜色

P3939 数颜色

也许上来就会想到数据结构,例如树套树,带修莫队等。不过如果真的写了任何一种上面的算法,应该是高级数据结构学傻了。

将兔子按照 (颜色, 位置) 进行双关键字排序。
操作 1 只需要在数组上二分查找;
操作 2 不会改变同种颜色兔子的相对位置,因此只需找到被交换的兔子改掉坐标即可。

Code

#include<cstdio>
#include<vector>
#include<algorithm>

using namespace std;

const int MAXN = 3e5 + 10;
int n, m;
int col[MAXN];
vector<int> pos[MAXN];

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

int main(){
    n = read(), m = read();
    for(register int i = 1; i <= n; i++){
        col[i] = read();
        pos[col[i]].push_back(i);
    }

    for(register int i = 1; i <= m; i++){
        int opt;
        opt = read();

        if(opt == 1){
            int l, r, c;
            l = read(), r = read(), c = read();

            int pos_l = lower_bound(pos[c].begin(), pos[c].end(), l) - pos[c].begin();
            int pos_r = upper_bound(pos[c].begin(), pos[c].end(), r) - pos[c].begin();

            printf("%d\n", pos_r - pos_l);
        }
        else{
            int x, y;
            x = read(), y = x + 1;
            if(y > n) y = 1;

            if(col[x] == col[y]) continue;

            int pos_x = lower_bound(pos[col[x]].begin(), pos[col[x]].end(), x) - pos[col[x]].begin();
            int pos_y = lower_bound(pos[col[y]].begin(), pos[col[y]].end(), y) - pos[col[y]].begin();
            swap(pos[col[x]][pos_x], pos[col[y]][pos_y]);
            swap(col[x], col[y]);
        }
    }

    return 0;
}

当然,还可以用平衡树,权值线段树,主席树搞过去。再提供一种暴力分块做法,块数在 \(230\) 左右基本上就没啥问题了。

Code

#include<cmath>
#include<cstdio>
#include<algorithm>

#define register

using namespace std;

const int MAXN = 3e5 + 10, SIZE = 410;
int n, m, siz, tot;
int col[MAXN];
int belong[MAXN], st[SIZE], ed[SIZE];
unsigned short temp[SIZE][MAXN];

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

inline void write(int num){
    if(!num){
        putchar('0');
        return;
    }
    if(num < 0) putchar('-'), num = -num;

    int stk[15], top = 0;
    while(num)
        stk[++top] = num % 10, num /= 10;
    while(top)
        putchar(stk[top--] + 48);
}

void init(){
    if(n > 160000) tot = 225, siz = n / tot;
    else tot = sqrt(n), siz = n / tot;

    for(register int i = 1; i <= tot; i++){
        st[i] = siz * (i - 1) + 1;
        ed[i] = siz * i;
    }
    ed[tot] = n;

    for(register int i = 1; i <= tot; i++){
        for(register int j = st[i]; j <= ed[i]; j++){
            belong[j] = i;
            temp[i][col[j]]++;
        }
    }
}

int Query_Col(int l, int r, int val){
    int ans = 0;

    if(belong[l] == belong[r]){
        for(register int i = l; i <= r; i++)
            if(col[i] == val) ans++;
    }
    else{
        for(register int i = l; i <= ed[belong[l]]; i++)
            if(col[i] == val) ans++;
        for(register int i = st[belong[r]]; i <= r; i++)
            if(col[i] == val) ans++;
        for(register int i = belong[l] + 1; i < belong[r]; i++)
            ans += temp[i][val];
    }

    return ans;
}

void Update(int pos){
    int x = pos, y = pos + 1;
    if(y > n) y = 1;

    if(belong[x] != belong[y]){
        temp[belong[x]][col[x]]--;
        temp[belong[x]][col[y]]++;
        temp[belong[y]][col[y]]--;
        temp[belong[y]][col[x]]++;
    }

    swap(col[x], col[y]);
}

int main(){
    n = read(), m = read();
    for(register int i = 1; i <= n; i++)
        col[i] = read();
    
    init();

    for(register int i = 1; i <= m; i++){
        int opt;
        opt = read();

        if(opt == 1){
            int l, r, c;
            l = read(), r = read(), c = read();
            write(Query_Col(l, r, c)), putchar('\n');
        }
        else{
            int x;
            x = read();
            Update(x);
        }
    }

    return 0;
}

C.分组

P3940 分组

\(k = 1\) 时,外循环枚举每一只兔子的颜色值 \(a_i\),内循环设 \(x\)\(1\) 枚举到 \(512\)\(=\sqrt{131072×2}\)),看看有没有访问过 \((x^2-col_i)\) 这个值。如果访问过,则第i只兔子与当前组内其它兔子有冲突,需要清空访问标记并且单独新建一个分组;如果没访问过,则令第i只兔子加入当前分组。最后再打上访问标记即可。

\(k = 2\) 时,思路和 \(k = 1\) 相似,但要用到关系并查集。如果访问过 \((x^2-col_i)\),我们仍然可以试图将 \(a_i\) 加入当前分组,因为此时我们的分组里允许有两个小团体。我们此时可以使用并查集来维护敌对关系:fa[] 数组开2倍空间, find(1~n) 代表每只兔子所在小团体, find(n+1~2n) 代表每只的兔子所在小团体的敌对小团体

在上述情况下当访问过 \((x^2-col_i)\) 时,我们先判断下颜色值为 \((x^2-col_i)\) 的兔子(开动态数组 vector 存下每一只当前分组内相同颜色的兔子编号。)和第 i 只兔子是否已经被规划进入同一小团体中。如果已经被规划入同一小团体,那么便不得不清空访问并新建分组。否则,将第 \(i\) 只兔子与颜色值为 \((x^2-col_i)\) 的兔子分别互相加入对方的敌对集合即可。

Code

#include<stack>
#include<cstdio>
#include<vector>

using namespace std;

const int MAXN = 140010;
int k, n, tot, last;
int col[MAXN];
stack<int> ans;
vector<int> vis[MAXN << 1];

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

struct Union_Set{
    int fa[MAXN << 1];

    void init(int m){
        for(register int i = 1; i <= m; i++)
            fa[i] = i;
    }

    int Find(int x){
        return fa[x] == x ? x : fa[x] = Find(fa[x]);
    }
}U;

void Clear(int &pre, int l){
    for(register int i = pre - 1; i > l; i--)
        vis[col[i]].clear();
    pre = l + 1;
    ans.push(pre - 1);
}

void Solve1(){
    for(register int i = n; i >= 1; i--){
        bool IsFind = true;
        for(register int j = 1; j <= 512; j++){
            if(j * j - col[i] >= 0 && vis[j * j - col[i]].size()){
                IsFind = false;
                break;
            }
        }

        if(!IsFind) Clear(last, i);
        vis[col[i]].push_back(1);
    }
}

void Solve2(){
    U.init(n << 1);
    
    for(register int i = n; i >= 1; i--){
        for(register int j = 1; j <= 512; j++)
            if(j * j - col[i] >= 0 && vis[j * j - col[i]].size())
                for(register int k = 0; k < vis[j * j - col[i]].size(); k++){
                    int t = vis[j * j - col[i]][k];
                    if(U.Find(i) == U.Find(t)) Clear(last, i);
                    else{
                        U.fa[U.Find(i + n)] = U.Find(t);
                        U.fa[U.Find(t + n)] = U.Find(i);
                    }
                }
        vis[col[i]].push_back(i);
    }
}

void Print(){
    printf("%ld\n", ans.size() + 1);
    while(!ans.empty())
        printf("%d ", ans.top()), ans.pop();
}

int main(){
    n = read(), k = read(), last = n + 1;
    for(register int i = 1; i <= n; i++)
        col[i] = read();
    
    if(k == 1) Solve1();
    else Solve2();

    Print();

    return 0;
}
posted @ 2022-09-04 06:55  TSTYFST  阅读(38)  评论(1编辑  收藏  举报