NOI2020训练题3 A 神奇的矩阵
题目描述:
有一个神奇的矩形。它的第一行每一个元素\(a_{1,i}\)都是给定的。对于每一个元素\(a_{x,y} \ (x>1)\),它的值都是\(a_{x−1,y}\)在\(a_{x−1,1}, \dots,a_{x−1,y}\)中出现过的次数。但由于这个矩阵很大,人们并不开心这么慢吞吞地计算整个矩阵的值,因此他们找到了你,并要求你快速知道某一个位置的值。有时这个矩阵的第一行还会被修改,你当然也要考虑这些修改的因素。
输入格式:
输入文件的第一行有三个正整数\(n, m, k\),分别表示这个矩形的行数,列数和操作个数。
接下去一行\(m\)个数表示第一行的每一个数字。
接下去\(k\)行,每行的第一个正整数为\(t\),表示操作类型。
如果\(t=0\),则表示一次询问操作,接下去有两个正整数\(x,y\),表示询问矩阵中第\(x\)行第\(y\)列的元素的值。
否则\(t=1\),接下去有两个正整数\(y,v\),表示把第一行第\(y\)个数字的值改为\(v\)。矩阵中其他数字也会相应改变。
输出格式:
对于每一个询问,输出一个整数表示矩阵中对应的值。
样例输入1:
3 3 9
1 1 1
0 1 1
0 1 2
0 1 3
0 2 1
0 2 2
0 2 3
0 3 1
0 3 2
0 3 3
样例输出1:
1
1
1
1
2
3
1
1
1
样例输入2:
3 3 7
1 2 3
0 3 3
1 1 3
0 3 3
1 2 3
0 3 3
1 3 1
0 3 3
样例输出2:
3
1
1
2
数据范围:
20%的数据,nmk≤107.
博客
40%的数据,mk≤107.
对于另外30%的数据,t≠1.
100%的数据,1≤n,m,k≤105,1≤x≤n,1≤y≤m,1≤v,a1,i≤109.
时间限制: 2s
空间限制:256MB
首先,题目定义的这种矩阵有一个神奇的性质,第 4 行与第 2 行相同,于是第 5 行也就与第 3 行相同,后面的也是一样。
因此矩阵可以看做只有 3 行,从上到下就是 1 2 3 2 3 2 3 ......
然后我们使用分块,将每一行分成 sqrt(m) 大小的块。
然后维护 A[i][j] —— 第一行前 i 块中,数字 j 的出现次数。
同时维护 B[i][j] —— 第二行前 i 块中,数字 j 的出现次数。
这里要将第一行的数字进行离散化减小 j 的范围。(同时要注意,询问第一行的数字时,不要直接输出了离散化之后的数字QAQ,要输出原本的数字,我就是这么WA的)
然后对于询问第二行的 x 位置,就先加上第一行 [1, x] 中前面的整个 k 块中这个数字的个数,再 O(sqrt n) 枚举最后一个块中前面到 x 的一段。
对于询问第三行的 x 位置,先计算第二行 x 位置的数值 Num ,加上第二行 [1, x] 中前面的整个 k 块中的 Num 个数,后面再求出最后一个块中前面到 x 的一段中有几个 Num,注意这里不能每个位置都 O(sqrt n) 求,而是 O(sqrt n) 扫一遍,同时用一个 Cnt[MaxNum] 的数组将扫到的数字对应的累加器+1,这样扫到一个位置就可以立即算出第二行这个位置的值了,最后再扫一遍将累加器减回去。
对于修改第一行的某个位置,显然可以向后扫每个块然后更新一下 A[][] 数组,然而 B[][] 的维护其实也是可以枚举后面的每个块然后总体 O(sqrt n) 维护的。
将修改操作分为插入和删除操作就可以很清晰地维护了。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int block = 1000;
int n,m,k;
int a[N];
int opt[N],l[N],r[N];
int lsh[N << 1], cnt;
int R;
int f[105][N << 1], g[105][N];
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i = 1; i <= m; ++ i) scanf("%d",&a[i]), lsh[++ cnt] = a[i];
for(int i = 1; i <= k; ++ i){
scanf("%d%d%d",&opt[i],&l[i],&r[i]);
if(opt[i] == 1) lsh[++ cnt] = r[i];
}
sort(lsh + 1, lsh + cnt + 1); cnt = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;
for(int i = 1; i <= m; ++ i) a[i] = lower_bound(lsh + 1, lsh + cnt + 1, a[i]) - lsh;
for(int i = 1; i <= k; ++ i){
if(opt[i] == 1) r[i] = lower_bound(lsh + 1, lsh + cnt + 1, r[i]) - lsh;
}
//lsh over
R = (m - 1) / block + 1;
for(int i = 1; i <= R; ++ i){
int ll = (i - 1) * block + 1;
int rr = min(i * block, m);
memcpy(f[i],f[i - 1],sizeof(f[i - 1]));
memcpy(g[i],g[i - 1],sizeof(g[i - 1]));
for(int j = ll; j <= rr; ++ j){
++ f[i][a[j]]; ++ g[i][f[i][a[j]]];
}
}
for(int i = 1; i <= k; ++ i){
if(opt[i] == 0){
if(l[i] == 1) printf("%d\n",lsh[a[r[i]]]);
else{
int x = (r[i] - 1) / block + 1;
for(int j = (x - 1) * block + 1; j <= r[i]; ++ j){
++ f[x - 1][a[j]];
++ g[x - 1][f[x - 1][a[j]]];
}
printf("%d\n",(l[i] & 1) ? (g[x - 1][f[x - 1][a[r[i]]]]) : ( f[x - 1][a[r[i]]]) );
for(int j = (x - 1) * block + 1; j <= r[i]; ++ j){
-- g[x - 1][f[x - 1][a[j]]];
-- f[x - 1][a[j]];
}
}
}
if(opt[i] == 1){
int x = (l[i] - 1) / block + 1;
for(int j = x; j <= R; ++ j){
-- g[j][f[j][a[l[i]]]];
-- f[j][a[l[i]]];
}
a[l[i]] = r[i];
for(int j = x; j <= R; ++ j){
++ f[j][a[l[i]]];
++ g[j][f[j][a[l[i]]]];
}
}
}
return 0;
}