文章目录
时间安排
一眼会了 T 1 T1 T1,先花 40 m i n 40min 40min 想了想 T 2 T2 T2,感觉会了 75 p t s 75pts 75pts。又回去看 T 1 T1 T1,先写了暴力,中间上了个厕所。最后将近 2 2 2 个半小时把 T 1 T1 T1 过了。拐回去写 T 2 T2 T2,写了 60 p t s 60pts 60pts,剩下 15 p t s 15pts 15pts 调不出来,急了,剩下 10 m i n 10min 10min 火速把 T 3 T3 T3 20 20 20 分过了。
最后得分:100 + 50 + 20 = 170
T 2 T2 T2 分段顺序写错了挂了 10 p t s 10pts 10pts。
题解
T2.「梦熊省选难度挑战赛 2023」树莓立方体
题意:
给定 k k k 行 n n n 列的正整数矩阵
( a i , j ) 1 ≤ i ≤ k , 1 ≤ j ≤ n (a_{i, j})_{1 \leq i \leq k,1 \leq j \leq n} (ai,j)1≤i≤k,1≤j≤n
有 q q q 个 询问任务,对每个询问任务 [ l , r ] [l, r] [l,r],需要计算一下公式:
∑ l ≤ x ≤ y ≤ r F ( max i = 1 k ( min j = x y a i , j ) ) \sum\limits_{l \leq x \leq y \leq r} F(\max\limits_{i = 1}^{k}(\min\limits_{j = x}^{y}a_{i, j})) l≤x≤y≤r∑F(i=1maxk(j=xminyai,j))
- 其中 F ( M ) = A ⊕ ( B M + C ) F(M) = A \oplus (BM + C) F(M)=A⊕(BM+C), A , B , C A, B,C A,B,C 是三个非负整数的常数, ⊕ \oplus ⊕ 表示二进制按位异或。
1 ≤ k ≤ 20 1 \leq k \leq 20 1≤k≤20, 1 ≤ n ≤ 50000 1 \leq n \leq 50000 1≤n≤50000, 1 ≤ q ≤ 50000 1 \leq q \leq 50000 1≤q≤50000, 1 ≤ a i , j ≤ 1 0 7 1 \leq a_{i, j} \leq 10^7 1≤ai,j≤107, 0 ≤ A ≤ 1 0 8 0 \leq A \leq 10^8 0≤A≤108, 0 ≤ B , C ≤ 10 0 \leq B,C \leq 10 0≤B,C≤10
分析:
感觉给出的 F F F 函数就是阻止你按位做,实际上本题确实不能按位考虑。
考虑从大到小插入每一个数字。假设每次插入 ( x , y ) (x, y) (x,y) 位置上的数字后, ( x , y ) (x, y) (x,y) 所在行已经插入的数字形成连通块中包含 ( x , y ) (x, y) (x,y) 的左右端点为 [ L , R ] [L, R] [L,R]。那么我们将所有区间 [ l , r ] [l, r] [l,r] 满足 L ≤ l ≤ r ≤ R L \leq l \leq r \leq R L≤l≤r≤R 遍历 。注意到一个区间 [ l , r ] [l, r] [l,r] 第一次被遍历的时刻是这 k k k 行 [ l , r ] [l, r] [l,r] 区间最小值的最大值被插入的时刻。
将一个区间 [ l , r ] [l, r] [l,r] 看作平面上 y y y 轴 和直线 y = x y = x y=x 之间的一个点。那么每次插入一个数 a x , y a_{x, y} ax,y 后就相当于把 点 ( L , R ) (L, R) (L,R) 的右下角所有还未被标记的点 标记成 F ( a x , y ) F(a_{x, y}) F(ax,y)。这里先不用考虑 y = x y = x y=x 下方的点,当它们存在即可。每个询问 [ l , r ] [l, r] [l,r] 就是问 ( l , r ) (l, r) (l,r) 右下角所有点的标记之和。
由于每次都是标记一个点所有右下角的点,因此任意时刻所有被标记的点都呈现了 阶梯状 。维护阶梯的顶点,然后每次插入 ( L , R ) (L, R) (L,R) 时可以根据这些顶点将右下角未被标记的区域拆分成若干矩形。注意到 每多拆一个矩形就会删掉一个顶点,因此矩形总个数均摊 O ( n k ) O(nk) O(nk)。
求出来所有的矩形后,问题变成了:有 n n n 次矩形加, q q q 次询问一个矩形内的和。
做法是将询问的矩形容斥成 4 4 4 个左上角固定(都在平面的左上方)的矩形,然后从左到右扫描列,对每一行维护 历史版本和 。在每一列对相应的矩形求答案。可以扫到一列就把不合法的行( y = x y = x y=x 下方)删除掉,这样就不会算到不合法的点的贡献了。
可以用 s e t set set 维护矩形端点,用线段树 + + + ( + , ∗ ) (+,*) (+,∗)矩乘 维护历史版本和
时间复杂度 O ( ( n k + q ) × log 2 n × 3 3 ) O((nk + q)\times \log_2n \times 3^3) O((nk+q)×log2n×33),有点卡常。
这里放一下我的 8.5 k 8.5k 8.5k 屎山代码:
cpp
// 首先有一个问题:若干矩形加,求单个矩形和
// 扫描线,维护历史版本和即可
#include<bits/stdc++.h>
#define MP make_pair
#define pb emplace_back
using namespace std;
struct IO{
static const int S=1<<21;
char buf[S],*p1,*p2;int st[105],Top;
~IO(){clear();}
inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}
inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
inline IO&operator >> (char&x){while(x=gc(),x==' '||x=='\n'||x=='\r');return *this;}
template<typename T>inline IO&operator >> (T&x){
x=0;bool f=0;char ch=gc();
while(!isdigit(ch)){if(ch=='-') f^=1;ch=gc();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
f?x=-x:0;return *this;
}
inline IO&operator << (const char c){pc(c);return *this;}
template<typename T>inline IO&operator << (T x){
if(x<0) pc('-'),x=-x;
do{st[++st[0]]=x%10,x/=10;}while(x);
while(st[0]) pc('0'+st[st[0]--]);return *this;
}
}fin,fout;
const int M = 22;
const int N = 50010;
typedef long long LL;
typedef pair< int, int > PII;
int m, n, q, odr[N * M];
LL A, B, C, a[M][N], ans[N];
struct qry {
int x, idx, f;
};
vector< qry > qq[N];
inline LL F(LL x) {return (A ^ (B * x + C));}
namespace Square {
struct matrix {
LL mt[3][3];
friend matrix operator * (const matrix a, const matrix b) {
matrix c;
c.mt[0][0] = a.mt[0][0] * b.mt[0][0] + a.mt[0][1] * b.mt[1][0] + a.mt[0][2] * b.mt[2][0];
c.mt[0][1] = a.mt[0][0] * b.mt[0][1] + a.mt[0][1] * b.mt[1][1] + a.mt[0][2] * b.mt[2][1];
c.mt[0][2] = a.mt[0][0] * b.mt[0][2] + a.mt[0][1] * b.mt[1][2] + a.mt[0][2] * b.mt[2][2];
c.mt[1][0] = a.mt[1][0] * b.mt[0][0] + a.mt[1][1] * b.mt[1][0] + a.mt[1][2] * b.mt[2][0];
c.mt[1][1] = a.mt[1][0] * b.mt[0][1] + a.mt[1][1] * b.mt[1][1] + a.mt[1][2] * b.mt[2][1];
c.mt[1][2] = a.mt[1][0] * b.mt[0][2] + a.mt[1][1] * b.mt[1][2] + a.mt[1][2] * b.mt[2][2];
c.mt[2][0] = a.mt[2][0] * b.mt[0][0] + a.mt[2][1] * b.mt[1][0] + a.mt[2][2] * b.mt[2][0];
c.mt[2][1] = a.mt[2][0] * b.mt[0][1] + a.mt[2][1] * b.mt[1][1] + a.mt[2][2] * b.mt[2][1];
c.mt[2][2] = a.mt[2][0] * b.mt[0][2] + a.mt[2][1] * b.mt[1][2] + a.mt[2][2] * b.mt[2][2];
return c;
}
friend matrix operator + (const matrix a, const matrix b) {
matrix c;
c.mt[0][0] = a.mt[0][0] + b.mt[0][0];
c.mt[0][1] = a.mt[0][1] + b.mt[0][1];
c.mt[0][2] = a.mt[0][2] + b.mt[0][2];
c.mt[1][0] = a.mt[1][0] + b.mt[1][0];
c.mt[1][1] = a.mt[1][1] + b.mt[1][1];
c.mt[1][2] = a.mt[1][2] + b.mt[1][2];
c.mt[2][0] = a.mt[2][0] + b.mt[2][0];
c.mt[2][1] = a.mt[2][1] + b.mt[2][1];
c.mt[2][2] = a.mt[2][2] + b.mt[2][2];
return c;
}
friend bool operator != (const matrix a, matrix b) {
if(a.mt[0][0] != b.mt[0][0]) return 1;
if(a.mt[0][1] != b.mt[0][1]) return 1;
if(a.mt[0][2] != b.mt[0][2]) return 1;
if(a.mt[1][0] != b.mt[1][0]) return 1;
if(a.mt[1][1] != b.mt[1][1]) return 1;
if(a.mt[1][2] != b.mt[1][2]) return 1;
if(a.mt[2][0] != b.mt[2][0]) return 1;
if(a.mt[2][1] != b.mt[2][1]) return 1;
if(a.mt[2][2] != b.mt[2][2]) return 1;
return 0;
}
} Mt, I;
struct Seg {
struct SegmentTree {
int l, r;
matrix mat, tag;
#define l(x) t[x].l
#define r(x) t[x].r
#define mat(x) t[x].mat
#define tag(x) t[x].tag
}t[N * 4];
void update(int p) {mat(p) = mat(p << 1) + mat(p << 1 | 1);}
void spread(int p) {
if(tag(p) != I) {
mat(p << 1) = mat(p << 1) * tag(p); tag(p << 1) = tag(p << 1) * tag(p);
mat(p << 1 | 1) = mat(p << 1 | 1) * tag(p); tag(p << 1 | 1) = tag(p << 1 | 1) * tag(p);
tag(p) = I;
}
}
void build(int p, int l, int r) {
l(p) = l, r(p) = r; tag(p) = I;
if(l == r) {
mat(p).mt[0][0] = mat(p).mt[0][1] = 0;
mat(p).mt[0][2] = 1;
return ;
}
int mid = (l + r >> 1);
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
update(p);
}
void change(int p, int l, int r, matrix c) {
if(l <= l(p) && r >= r(p)) {
tag(p) = tag(p) * c;
mat(p) = mat(p) * c;
return ;
}
spread(p);
int mid = (l(p) + r(p) >> 1);
if(l <= mid) change(p << 1, l, r, c);
if(r > mid) change(p << 1 | 1, l, r, c);
update(p);
}
void clr(int p, int pos) {
if(pos < l(p) || pos > r(p)) return ;
if(l(p) == r(p)) {
mat(p).mt[0][1] = 0, mat(p).mt[0][2] = 0;
return ;
}
spread(p);
int mid = (l(p) + r(p) >> 1);
if(pos <= mid) clr(p << 1, pos);
else clr(p << 1 | 1, pos);
update(p);
}
LL ask(int p, int l, int r) {
if(l <= l(p) && r >= r(p)) return mat(p).mt[0][0];
spread(p);
int mid = (l(p) + r(p) >> 1);
if(r <= mid) return ask(p << 1, l, r);
else if(l > mid) return ask(p << 1 | 1, l, r);
else return ask(p << 1, l, r) + ask(p << 1 | 1, l, r);
}
} T;
struct line {
int x, u, d; // u > d
LL v;
}L[N * M * 10];
int tot;
int bin[N * M], mn[N * M], mx[N * M];
bool bok[M][N];
set< PII > s;
inline int X(int x) {return x % n == 0 ? x / n : x / n + 1;};
inline int Y(int x) {return x % n == 0 ? n : x % n;}
int Find(int x) {return x == bin[x] ? x : bin[x] = Find(bin[x]);}
inline void Merge(int x, int y) {
int f1 = Find(x), f2 = Find(y);
if(f1 != f2) {bin[f1] = f2; mn[f2] = min(mn[f2], mn[f1]); mx[f2] = max(mx[f2], mx[f1]);}
}
bool cmp(int x, int y) {return a[X(x)][Y(x)] > a[X(y)][Y(y)];}
bool ccmp(line a, line b) {return a.x < b.x;};
inline void ins_square(int xl, int yl, int xr, int yr, LL v) {
if(xl > xr || yl < yr) return ;
L[++ tot] = (line) {xl, yl, yr, v};
L[++ tot] = (line) {xr + 1, yl, yr, -v};
}
void ins(int x, int y, LL v) { // 插入一个阶梯状拐点
if(s.empty()) {
s.insert(MP(x, y));
ins_square(x, y, n + 1, 0, v);
return ;
}
auto itl = s.lower_bound(MP(x, y));
if(itl == s.end()) { // 找不到 左边肯定有一个
itl --;
if(itl->second >= y) return ; // 没必要插了
ins_square(x, y, n + 1, itl->second + 1, v);
s.insert(MP(x, y));
return ;
}
if(itl->first == x) return ; // 已经包含了没必要插
auto itr = itl;
if(itl != s.begin()) { // 先把左边处理了
int tx = (itl->first); itl --;
if(itl->second >= y) return ;
int gy = itl->second;
if(itl->first < x) itl ++; // 如果等于 x,那么需要把它删掉
ins_square(x, y, tx - 1, gy + 1, v);
}
else {
int tx = (itl->first);
ins_square(x, y, tx - 1, 0, v);
}
while(itr->second <= y) {
int tx = (itr->first); int ty = (itr->second);
itr ++;
if(itr != s.end()) {
int nxtx = (itr->first);
ins_square(tx, y, nxtx - 1, ty + 1, v);
}
else {
ins_square(tx, y, n + 1, ty + 1, v);
break;
}
}
s.erase(itl, itr);
s.insert(MP(x, y));
}
inline matrix get_matrix(LL v) {
matrix res;
res.mt[0][0] = 1; res.mt[0][1] = 0; res.mt[0][2] = 0;
res.mt[1][0] = 0; res.mt[1][1] = 1; res.mt[1][2] = 0;
res.mt[2][0] = 0; res.mt[2][1] = v; res.mt[2][2] = 1;
return res;
}
void get() {
for(int i = 1; i <= n * m; i ++ ) bin[i] = i, mn[i] = mx[i] = Y(i);
sort(odr + 1, odr + n * m + 1, cmp);
for(int i = 1; i <= n * m; i ++ ) {
int o = odr[i];
int x = X(o), y = Y(o);
bok[x][y] = 1;
if(bok[x][y - 1]) Merge((x - 1) * n + y - 1, o);
if(bok[x][y + 1]) Merge((x - 1) * n + y + 1, o);
int f = Find(o);
ins(mn[f], mx[f], F(a[x][y]));
}
}
inline void scan() { // 扫描线, 需要维护历史版本和
for(int i = 0; i < 3; i ++ ) for(int j = 0; j < 3; j ++ ) I.mt[i][j] = (i == j);
Mt.mt[0][0] = 1; Mt.mt[1][0] = 1;
Mt.mt[1][1] = 1; Mt.mt[2][2] = 1;
sort(L + 1, L + tot + 1, ccmp);
T.build(1, 0, n);
int p = 1;
for(int i = 1; i <= n; i ++ ) { // 扫描每一列
T.clr(1, i - 1);
while(p <= tot && L[p].x <= i) { // 先变换
T.change(1, L[p].d, L[p].u, get_matrix(L[p].v));
p ++;
}
T.change(1, 0, n, Mt);
for(auto v : qq[i]) {
int x = v.x, idx = v.idx, f = v.f;
ans[idx] += T.ask(1, x, n) * f;
}
}
}
}
int main() {
freopen("plant.in", "r", stdin);
freopen("plant.out", "w", stdout);
fin >> m >> n >> q;
for(int i = 1; i <= m; i ++ )
for(int j = 1; j <= n; j ++ ) {
fin >> a[i][j];
odr[(i - 1) * n + j] = (i - 1) * n + j;
}
fin >> A >> B >> C;
for(int i = 1; i <= q; i ++ ) {
int l, r; fin >> l >> r;
qq[r].pb((qry) {l, i, 1});
if(r + 1 <= n) qq[r].pb((qry) {r + 1, i, -1});
if(l - 1 >= 1) qq[l - 1].pb((qry) {l, i, -1});
if(l - 1 >= 1 && r + 1 <= n) qq[l - 1].pb((qry) {r + 1, i, 1});
}
Square::get();
Square::scan();
for(int i = 1; i <= q; i ++ ) fout << ans[i], fout.pc('\n');
return 0;
}