小 Y 的房间题解

1.前言

这还是我第一次做到利用调整块的大小来降低分块的时间复杂度的题,费了很多时间,也算值得。(不过正解的代码比这简单的多, a n s ans ans 直接用前缀和修改就可以了,我是 s b sb sb,最开始没想到,然后就打了一个又臭又长的代码。)

2.题解

操作和原数组都分个块, S i z e q Size_q Sizeq 表示操作的每个块的大小, S i z e Size Size 表示原数组每个块的大小。 a n s [ i ] [ j ] ans[i][j] ans[i][j] 表示原数组 i i i 号块到 j j j 号块的心烦程度。 G e t q ( x ) Get_q (x) Getq(x) 表示 x x x 在操作的第几个块, l q , r q ( x ) lq, rq(x) lq,rq(x) 表示操作块 x x x 的左右端点, G e t ( x ) Get (x) Get(x) 表示 x x x 在原数组的第几个块, l , r ( x ) l, r(x) l,r(x) 表示原数组块 x x x 的左右端点。

当操作从 i i i 号块变到 i + 1 i + 1 i+1 号块时,就暴力更新 a n s ans ans,暴力更新 p r e pre pre
单次暴力更新的时间复杂度是 O ( n 2 S i z e + S i z e q ⋅ n S i z e ) O (\frac{n^2}{Size} + Size_q \cdot \frac {n}{Size}) O(Sizen2+SizeqSizen),所有更新的时间复杂度是 O ( ( n 2 S i z e + S i z e q ⋅ n S i z e ) ⋅ q S i z e q ) O ((\frac{n^2}{Size} + Size_q \cdot \frac {n}{Size}) \cdot \frac {q}{Size_q}) O((Sizen2+SizeqSizen)Sizeqq)

每次询问 [ l , r ] [l, r] [l,r]

Ⅰ我们如果 G e t ( l ) = G e t ( r ) Get(l) = Get(r) Get(l)=Get(r) ,则先暴力统计答案,再做修改的操作。

Ⅱ 我们如果 G e t ( l ) ≠ G e t ( r ) Get(l) \neq Get (r) Get(l)=Get(r) ,则先暴力统计残缺块,再统计 [ G e t ( l ) + 1 , G e t ( r ) − 1 ] [Get(l) + 1, Get(r) - 1] [Get(l)+1,Get(r)1] 块的答案,再做修改的操作。

单次查询的时间复杂度 O ( S i z e ∗ 2 + S i z e q ∗ 2 ) O (Size * 2 + Size_q * 2) O(Size2+Sizeq2),所有查询的时间复杂度是 O ( ( S i z e ∗ 2 + S i z e q ∗ 2 ) ∗ q ) O ((Size * 2 + Size_q * 2) * q) O((Size2+Sizeq2)q)

总体时间复杂度为 O ( ( n 2 S i z e + S i z e q ⋅ n S i z e ) ⋅ q S i z e q + ( S i z e ∗ 2 + S i z e q ∗ 2 ) ∗ q ) O ((\frac{n^2}{Size} + Size_q \cdot \frac {n}{Size}) \cdot \frac {q}{Size_q} + (Size * 2 + Size_q * 2) * q) O((Sizen2+SizeqSizen)Sizeqq+(Size2+Sizeq2)q)。发现 S i z e q = q 2 3 Size_q = q^{\frac{2}{3}} Sizeq=q32 S i z e = n 2 3 Size = n^{\frac{2}{3}} Size=n32 的时间复杂度最优,为 O ( n 5 3 ) O(n^\frac{5}{3}) O(n35)

注释之后补


#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

#define LL long long 
template <typename T>
void read (T &x) {
	x = 0; T f = 1;
	char ch = getchar ();
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar ();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + ch - '0';
		ch = getchar ();
	}
	x *= f;
}
template <typename T>
void write (T x) {
	if (x < 0) {
		x = -x;
		putchar ('-');
	}
	if (x < 10) {
		putchar (x + '0');
		return;
	}
	write (x / 10);
	putchar (x % 10 + '0');
}
template <typename T>
void print (T x, char ch) {
	write (x); putchar (ch);
}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }

const int Maxn = 1e5;
const int Maxsn = 2000;

int n, m, q;
int a[Maxn + 5], w[Maxn + 5];

#define Getq(x) ((x - 1) / Size_q + 1)//Get_q (x) 表示 x 在操作的第几个块
#define lq(x) ((x - 1) * Size_q + 1)
#define rq(x) (Min ((x) * Size_q, q))
//lq, rq(x) 表示操作块 x 的左右端点 
int Size_q, Num_q;
struct question {
	int op, x, y;
}sq[Maxn + 5];
#define Get(x) ((x - 1) / Size + 1)//Get (x) 表示 x 在原数组的第几个块 
#define l(x) ((x - 1) * Size + 1)
#define r(x) (Min (((x) * Size), n))
//l, r(x) 表示原数组块 x 的左右端点。 
#define add(x) { res -= w[bak[x]]; bak[x]++; res += w[bak[x]]; }//区间内增加一个 x 类型的物品 
#define del(x) { res -= w[bak[x]]; bak[x]--; res += w[bak[x]]; }//区间内减少一个 x 类型的物品 
int Size, Num;
int Now[Maxn + 5];
int pre[Maxsn + 5][Maxn + 5];
//pre[i][j]表示前 i 个块中 j 类型的物品有多少个 
int ans[Maxsn + 5][Maxsn + 5];
//ans[i][j]表示第 i 个块到第 j 个块的烦心程度 
void Build_pre () {
    //pre初始化
    for (int i = 1; i <= Num; i++) {
        for (int j = 1; j <= m; j++)
        	pre[i][j] = pre[i - 1][j];
        for (int j = l (i); j <= r (i); j++)
        	pre[i][a[j]]++;
    }
}
void Build_ans () {
    //ans初始化+暴力更新 
    int bak[Maxn + 5];
    for (int i = 1; i <= Num; i++) {
        memset (bak, 0, sizeof bak);
        int res = 0;
        for (int j = i; j <= Num; j++) {
            for (int k = l (j); k <= r (j); k++)
                add (a[k]);
            ans[i][j] = res;
        }
    }
}
void Change (int s, int x, int y) {
	//第 s 个块少了一个 x 类型,多了一个 y 类型对 pre 的影响。 
    for (int i = s; i <= Num; i++) {
        pre[i][x]--;
        pre[i][y]++;
    }
}
void Re_Building (int x) {
	//操作块更新 
    for (int i = lq (x); i <= rq (x); i++) {
        if (sq[i].op == 1) {
            int id = sq[i].x, y = sq[i].y;
            Change (Get (id), a[id], y);
            a[id] = y;
            Now[id] = a[id];
        }
    }
    Build_ans ();
}
#define adds(x) { res -= w[bak[x]]; bak[x]++; res += w[bak[x]]; que[++tt] = x; }//区间内增加一个 x 类型的物品,并记录下修改的下标 
#define dels(x) { res -= w[bak[x]]; bak[x]--; res += w[bak[x]]; que[++tt] = x; }//区间内减少一个 x 类型的物品,并记录下修改的下标 
#define memory(l,r) { for (int i = l; i <= r; i++) Now[sq[i].x] = a[sq[i].x]; for (int i = 0; i < tt - hh + 1; i++) bak[que[i]] = 0, vis[que[i]] = 0; }//清空bak,vis,还原 Now 
int bak[Maxn + 5];
bool vis[Maxn + 5];
int que[Maxn + 5], hh, tt;
int solve (int l, int r, int ql, int qr) {
    int res = 0;
    hh = 0; tt = -1;
    if (Get (l) == Get (r)) {// l,r 在同一个块 
        for (int i = l; i <= r; i++)
            adds (a[i]);
        for (int i = ql; i <= qr; i++) {
        //修改操作 
            if (sq[i].op == 1 && l <= sq[i].x && sq[i].x <= r) {
                int id = sq[i].x, y = sq[i].y;
                dels (Now[id]);
                Now[id] = y;
                adds (y);
            }
        }
        memory (ql, qr);
        return res;
    }
    else {
        res = ans[Get (l) + 1][Get (r) - 1];
        for (int i = l; i <= r (Get (l)); i++) {
			if (vis[a[i]] == 0) bak[a[i]] = pre[Get (r) - 1][a[i]] - pre[Get (l)][a[i]], vis[a[i]] = 1;
			//如果 a[i] 之前没有出现过,则 bak 需要将 [Get(l) + 1, Get(r) - 1] 的块中的类型为 a[i] 的物品的数量加上,之后的类似的 if 同理 
            adds (a[i]);
        }
        for (int i = l (Get (r)); i <= r; i++) {
			if (vis[a[i]] == 0) bak[a[i]] = pre[Get (r) - 1][a[i]] - pre[Get (l)][a[i]], vis[a[i]] = 1;
            adds (a[i]);
        }
        //残缺块暴力搞 
        for (int i = ql; i <= qr; i++) {
        //修改操作 
            if (sq[i].op == 1 && l <= sq[i].x && sq[i].x <= r) {
                int id = sq[i].x, y = sq[i].y;
                
				if (vis[Now[id]] == 0) bak[Now[id]] = pre[Get (r) - 1][Now[id]] - pre[Get (l)][Now[id]], vis[Now[id]] = 1;
                dels (Now[id]);
                
                Now[id] = y;
                
				if (vis[Now[id]] == 0) bak[Now[id]] = pre[Get (r) - 1][Now[id]] - pre[Get (l)][Now[id]], vis[Now[id]] = 1;
                adds (Now[id]);
            }
        }
        memory (ql, qr);
        return res;
    }
}

signed main () {
	read (n); read (m); read (q);
	for (int i = 1; i <= n; i++) read (a[i]);
	for (int i = 1; i <= n; i++) read (w[i]);
	Size_q = pow (q, 2.0 / 3); Size = pow (n, 2.0 / 3);
	Num_q = ceil (q * 1.0 / Size_q);
	Num = ceil (n * 1.0 / Size);
    memcpy (Now, a, sizeof a);
    Build_pre ();
    Build_ans ();
	
	int last = 0;
	for (int step = 1; step <= q; step++) {
		read (sq[step].op); read (sq[step].x); read (sq[step].y);
		if (step != 1 && Getq (step) != Getq (step - 1))//进入到了一个新的询问块 
		    Re_Building (Getq (step - 1));
		
		sq[step].x ^= last;
		sq[step].y ^= last;
		if (sq[step].op == 2) {
		    last = solve (sq[step].x, sq[step].y, lq (Getq (step)), step);
		    print (last, '\n');
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值