被暴打了qwq



还有好多题要补qwq

1006 - Xor sum

题意简介

给你一个长度为 \(n\) 的数组,要求你选出其中最小的区间,使得这个区间的异或和比 \(k\) 大。

思路分析

看到异或,想到线性基和 Trie,这道题用的是后者。

从左往右扫,对每个 r,先维护当前的前缀异或和,然后在 Trie 中模拟异或的过程,从高位往低位,对于让答案大于 k 的选项,直接尝试更新答案。

从高位往低位转移的过程中,往异或路径可以使前几位(也就是已经扫到的高位)与 k 值一致的方向移动即可。

具体实现看代码。

解题代码

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5+5, M = 30;
int to[N*M][2], pos[N*M], k, cn;
inline void debug(int x) {
for(int i = 29; i >= 0; i--) {
if((x & (1 << i))) putchar('1');
else putchar('0');
}
}
inline void insert(int x, int id) {
int u = 1, d = 0;
for(int i = 29; i >= 0; i--) {
d = ((1 << i) & x) ? 1 : 0;
if(to[u][d] == 0) to[u][d] = ++cn;
u = to[u][d];
pos[u] = id;
}
}
inline int get_L(int x) {
int u = 1, bit_k, bit_x, L = -1;
if(x >= k) L = 0;
// printf("Now finding %d to %d\n", x, k);
for(int i = 29; i >= 0 && u; i--) {
bit_k = ((1 << i) & k) ? 1 : 0;
bit_x = ((1 << i) & x) ? 1 : 0;
if(bit_x > bit_k) {
// 这一位已经更大了 那如果能找到一个0来异或 答案总是能比k大的。
if(to[u][0] != 0) L = max(L, pos[to[u][0]]);
// 针对异或后一样的情况 继续往下找
u = to[u][1];
// printf("1");
continue;
}
if(bit_k == 0) {
// 只要能找一个 1 来异或那必然是更大的
if(to[u][1] != 0) L = max(L, pos[to[u][1]]);
u = to[u][0];
// printf("0");
continue;
}
// 两都是1 那的要个0 只有自己是0 那得要个1
if(bit_x == bit_k) u = to[u][0];
else u = to[u][1];
// printf(bit_x == bit_k ? "*0*" : "*1*");
}
// 成功走到了末尾 也就是异或
if(u) L = max(L, pos[u]);
// puts("");
// printf("Get L = %d\n", L);
return L;
}
inline void clear_Trie() {
for(int i = 1; i <= cn; i++) to[i][0] = to[i][1] = pos[i] = 0;
}
int main() {
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
int t, n, sum, l, ansl, len, x;
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &k);
// printf("K = %d ", k); debug(k); puts("");
clear_Trie();
sum = 0, len = N, cn = 1;
for(int i = 1; i <= n; i++) {
scanf("%d", &x);
sum ^= x;
// printf("Now: %d ", x); debug(x); puts("");
// printf("Sum: %d ", sum); debug(sum); puts("");
l = get_L(sum);
if(l != -1 && i - l < len) {
len = i - l;
ansl = l + 1;
}
insert(sum, i);
}
if(len != N) printf("%d %d\n", ansl, ansl + len - 1);
else printf("-1\n");
}
return 0;
}


1008 - Maximal submatrix

题意简介

给你一个 \(n\times m\) 的矩阵,要求你找出其中的最大子矩阵,使得这个最大子矩阵每列的元素都是随行数递增而不严格递增的。

其中 \(n,m \leq 2000\) , 要求输出这个子矩阵的位置。

思路分析

首先,把每列单独拆出来,分别 dp,求解其对应的最长非递减子段的长度。

我们记 \(f(i,j)\) 表示在第 j 列中,以第 i 个元素为末尾元素的,最长的非递减字段的长度。

显然地,我们有转移:


\[f(i, j) = \begin{cases}1, &A(i, j) < A(i-1, j) \\ f(i - 1, j) + 1, &A(i,j) \geq A(i-1,j) \end{cases}\]


现在,我们考虑如何求出答案。

一开始,我们尝试对每个位置 \((i,j)\) ,求出以 \(x = i, y = j\) 为为底边的最大矩形。但是想要做到这样,需要 \(O(n^3)\),我们需要一种线性或是带 log 的复杂度。

于是,我们想到,可以对于每个点 \((i,j)\) 求出其恰以 \(f(i,j)\) 为高的矩阵最大面积。因为高比 \(f(i,j)\) 小的矩阵我们一定会在这一行的别的点处被枚举到。

具体的操作,我们使用了单调栈。通过单调栈,可以找到同一行的,可向上扩展距离(也就是 \(f(x, y)\))大于等于 \(f(i, j)\) 的最远位置,然后求出面积 \(f(i, j) \times (R(i, j) - L(i, j) + 1)\),尝试更新答案。

解题代码

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2005, M = 2005;
int T, n, m, A[N][M], f[N][M], Left[M], Right[M], st[M], res;
int main() {
scanf("%d", &T);
while(T--) {
res = 0;
memset(f, 0, sizeof(f));
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) scanf("%d", A[i]+j);
for(int j=1; j<=m; j++) for(int i=1; i<=n; i++) {
f[i][j] = A[i][j] >= A[i-1][j] ? f[i-1][j] + 1 : 1;
}
for(int i=1; i<=n; i++) {
// st[i] 为第 i 行的单调栈
int &top = st[0];
top = 0;
memset(Left, 0, sizeof(Left));
memset(Right, 0, sizeof(Right));
for(int j=1; j<=m; j++) {
while(top && f[i][st[top]] >= f[i][j]) {
Left[j] = Left[st[top]];
top--;
}
if(!Left[j]) Left[j] = j;
st[++top] = j;
}
top = 0;
for(int j=m; j>=1; j--) {
while(top && f[i][st[top]] >= f[i][j]) {
Right[j] = Right[st[top]];
top--;
}
if(!Right[j]) Right[j] = j;
st[++top] = j;
}
for(int j=1; j<=m; j++) {
res = max(res, (Right[j] - Left[j] + 1) * f[i][j]);
}
}
printf("%d\n", res);
}
return 0;
}


1010 - zoto

题意简介

告诉你 \(n\) 个点 \((i,y_i)\),然后有 \(m\) 个询问,给出一个矩形的左下角和右上角的坐标,问你这个区域内能找到多少种不同的 \(y\) 值。

其中,\(n,m,y_i < 100000\)。

思路简介

一眼想到莫队。用树状数组维护区间答案。复杂度是 \(O(n\sqrt{n}\log{n})\)。但是,这样会 T 的,这道题的做法更巧妙。本题是除了莫队外,还对值域根号分块,强制将修改复杂度定为 \(O(1)\) ,即只修改单点和对应块的;然后查询时,整块直接取,余点暴力算。

这样凑出来,莫队的复杂度刚好是:\(O(n\sqrt{n} + m\sqrt{N})\) 。(\(N\) 是值域)。

解题代码

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int N = 1e5 + 5, M = 1e3 + 5;
int T, n, m, block[M], cnt[N], y[N], bn, by, Ans[N];
struct Query {
int xl, xr, yl, yr, i, bl;
Query() {
xl = xr = yl = yr = i = bl = 0;
}
bool operator < (const Query &B) const {
if(bl != B.bl) return bl < B.bl;
return (bl & 1) ? (xr < B.xr) : (xr > B.xr);
}
} Q[N];
inline void add_value(int y) {
cnt[y]++;
if(cnt[y] == 1) block[y / by]++;
}
inline void restore(int y) {
cnt[y]--;
if(cnt[y] == 0) block[y / by]--;
}
inline int get_Ans(int l, int r) {
int bl = l / by, br = r / by, res = 0;
if(bl == br) {
for(int i = l; i <= r; i++) res += cnt[i] ? 1 : 0;
return res;
}
for(int i = bl + 1; i < br; i++) res += block[i];
for(int i = bl * by + by - 1; i >= l; i--) res += cnt[i] ? 1 : 0;
for(int i = br * by; i <= r; i++) res += cnt[i] ? 1 : 0;
return res;
}
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
bn = (int) sqrt(n) + 1, by = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", y + i);
by = max(by , y[i]);
}
memset(block, 0, sizeof(block));
memset(cnt, 0, sizeof(cnt));
for(int i = 1; i <= m; i++) {
scanf("%d%d%d%d", &Q[i].xl, &Q[i].yl, &Q[i].xr, &Q[i].yr);
Q[i].bl = (Q[i].xl - 1) / bn + 1;
Q[i].i = i;
by = max(by, Q[i].yr);
}
by = (int) sqrt(by) + 1;
sort(Q + 1, Q + m + 1);
int l = 1, r = 0;
for(int i = 1; i <= m; i++) {
while(r < Q[i].xr) add_value(y[++r]);
while(l > Q[i].xl) add_value(y[--l]);
while(r > Q[i].xr) restore(y[r--]);
while(l < Q[i].xl) restore(y[l++]);
Ans[Q[i].i] = get_Ans(Q[i].yl, Q[i].yr);
}
for(int i = 1; i <= m; i++) {
printf("%d\n", Ans[i]);
}
}
return 0;
}