SDOI 2009 (COGS 421)HH的项链 分块or树状数组

人生第一次接触分块。以前也听过分块的思想,不过实践这还是第一次,调了老半天。
很显然我就是用分块思想写的这道题。不过貌似正解是树状数组,但是鉴于分块比较好像,就写的分块。毕竟第一次写分块,先谈谈自己对分块的理解。

刘汝佳叔叔貌似把分块算作数据结构。不过以我目前的理解程度,觉得分块更像是一种思想,一种优化暴力的思想。与分治不同,分治是把整个问题划分成小问题解决;而分块是把原问题划分成很多整块,对于每个块暴力求解,再求解块与块之间联系着的解,对于一次询问,在一段连续的整块内可以O(1)得出,而不满一块的两段部分暴力求解。分块做法就是把O(1)得到的那部分“整”的预处理出来,从原本的暴力优化成了O(1),“零”的暴力解。

分块做法大多数把原问题分成ceil(√n)块,每块大小是floor(√n),这样做貌似预期复杂度是最小的,这不是分块思想的重点。
对于这道题,我们把区间分为ceil(√n)块,首先对于每一块暴力求出不同数字的个数复杂度O(n)。设f[i][j]表示第i块到第j块不同数字的个数,我们要由f[i][i]求出f[i][j],还需要一些量:我们要知道第i个数左边第一个和它相同的数的位置l[i]以及右边的第一个r[i]。那么我们就可以求出f数组,具体做法很灵活,比如:f[i][j] = f[i+1][j],再在块i内寻找i+1~j块中没出现过的,加到f[i][j]中即可,这就用到了r数组。
预处理完毕后就可以读入询问了,对于一个询问[a,b],我们先求出a,b所在的块fa,fb,连续的整块fa+1~fb-1可以O(1)得到就是f[fa+1][fb-1],之后暴力求出a所在块以及b所在块内出现的加到res中即可,需要用到之前求出的l以及r数组。这个过程要不重不漏,需要注意细节。

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;

int n, m, num, siz;
int w[50005], f[405][405];
int l[50005], r[50005], last[1000005];
bool vis[1000005];

void get(int &x){
    x = 0; char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c <= '9' && c >= '0') x = x*10+c-48, c = getchar();
}

int main()
{
    get(n);
    num = ceil(sqrt(n));
    siz = floor(sqrt(n));
    memset(r, 0x3f, sizeof r);
    for(int i = 1; i <= n; i++){
        get(w[i]);
        l[i] = last[w[i]];
        last[w[i]] = i;
        r[l[i]] = i;
    }

    for(int i = 1; i <= num; i++){
        memset(vis, 0, sizeof vis);
        int beg = (i-1)*siz+1;
        int end = min(i*siz, n);
        for(int j = beg; j <= end; j++){
            f[i][i] += !vis[w[j]];
            vis[w[j]] = 1;
        }
    }

    for(int i = 1; i < num; i++)
    for(int j = 1; i+j <= num; j++){
        f[j][i+j] = f[j+1][i+j];
        int beg = (j-1)*siz + 1;
        int end = j * siz;
        int border = (i+j)*siz; 
        for(int k = beg; k <= end; k++){
            f[j][i+j] += (r[k] > border);
        }
    }

    get(m);
    while(m--){
        int a, b;
        get(a); get(b);
        int fa = a/siz+1;
        int fb = b/siz+1;
        int res = f[fa+1][fb-1];
        if(fb-fa <= 1){
            memset(vis, 0, sizeof vis);
            for(int i = a; i <= b; i++){
                res += !vis[w[i]];
                vis[w[i]] = 1;
            }
            printf("%d\n", res);
            continue;
        }

        int rb = (fb-1) * siz;
        int enda = fa * siz;
        int begb = (fb-1)*siz+1;
        for(int i = a; i <= enda; i++){
            res += (r[i] > rb);
        }
        for(int i = begb; i <= b; i++){
            res += (l[i] < a);
        }
        printf("%d\n", res);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值