首先有个 m * n logn 的做法, 就是暴力枚举右端点, 然后可持久化trie来查, 考虑如何优化
n 那么小是不是可以预处理一些东西, 我们又想到了分块
类似分块的套路, 可以预处理 f[i][j] , 表示第i块到第j块的答案
综合空间以及时间的考虑, 我们发现可以直接处理 f[i][j] 表示 第i块的起点到 j 的答案
然后询问可以直接查, 剩下的 根号n个暴力查, 复杂度
#include<bits/stdc++.h>
#define N 120050
#define M 200
using namespace std;
typedef long long ll;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
struct Trie{
int ch[N * 35][2], siz[N * 35];
int rt[N], tot;
void Insert(int &x, int last, int i, int val){
x = ++tot; siz[x] = siz[last] + 1;
if(i < 0) return; int k = (val >> i) & 1;
ch[x][k^1] = ch[last][k^1];
Insert(ch[x][k], ch[last][k], i-1, val);
}
int Quary(int a, int b, int i, int val){
if(i < 0) return 0; int k = (val >> i) & 1;
int sum = siz[ch[b][k^1]] - siz[ch[a][k^1]];
if(sum > 0) return Quary(ch[a][k^1], ch[b][k^1], i-1, val) + (1<<i);
else return Quary(ch[a][k], ch[b][k], i-1, val);
}
}T;
int n, m, a[N], sum[N], siz, pos[N], L[N], R[N];
int f[M][N], ans;
int main(){
n = read(), m = read();
T.Insert(T.rt[0], T.rt[0], 30, 0);
for(int i=1; i<=n; i++){
a[i] = read(); sum[i] = sum[i-1] ^ a[i];
T.Insert(T.rt[i], T.rt[i-1], 30, sum[i]);
}
siz = sqrt(n); int res = 0;
for(int i=1; i<=n; i++){
pos[i] = (i-1) / siz + 1;
for(int j=1; j<=pos[i]; j++){
int l = (j-1) * siz;
f[j][i] = max(f[j][i-1], T.Quary(T.rt[l], T.rt[i], 30, sum[i]));
}
}
for(int i=1; i<=n; i+=siz) L[++res] = i, R[res] = i + siz - 1; R[res] = n;
while(m--){
int x = read(), y = read();
int l = min(((ll)ans + (ll)x) % n + 1, ((ll)ans + (ll)y) % n + 1); l--;
int r = max(((ll)ans + (ll)x) % n + 1, ((ll)ans + (ll)y) % n + 1);
ans = 0; int p = pos[l]; ans = max(ans, f[p+1][r]);
for(int i=l; i<=min(r, R[p]); i++) ans = max(ans, T.Quary(T.rt[l-1], T.rt[r], 30, sum[i]));
printf("%d\n", ans);
} return 0;
}
k > 根号 n 直接暴力跳, 因为跳的次数不会超过 n / k
k < 根号 n 暴力预处理, val[u][k] 表示从u开始一直k步k步往上跳的答案, 像前缀和一样 dfs 一遍就可以了
因为跳需要倍增, 所以复杂度
#include<bits/stdc++.h>
#define N 100050
#define M 240
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
int S;
int first[N], nxt[N], to[N], tot;
int w[N], n, a[N], k[N];
int fa[N][20], dep[N], val[N][M];
void add(int x, int y){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y;}
void dfs(int u, int f){
for(int i=1; i<=18; i++)
fa[u][i] = fa[fa[u][i-1]][i-1];
for(int i=first[u];i;i=nxt[i]){
int t = to[i]; if(t == f) continue;
fa[t][0] = u; dep[t] = dep[u] + 1;
dfs(t, u);
}
}
int Up(int x, int k){
for(int i=18; i>=0; i--) if((1<<i) <= k) k -= (1<<i), x = fa[x][i];
return x;
}
int LCA(int x, int y){
if(dep[x] < dep[y]) swap(x, y);
for(int i=18; i>=0; i--) if(dep[fa[x][i]] >= dep[y]) x = fa[x][i];
if(x == y) return x;
for(int i=18; i>=0; i--) if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
void dfs2(int u, int f){
for(int i=1; i<=S; i++) val[u][i] = val[Up(u, i)][i] + w[u];
for(int i=first[u];i;i=nxt[i]){
int t = to[i]; if(t == f) continue;
dfs2(t, u);
}
}
int Solve(int x, int y, int k){
if(dep[y] > dep[x]) return 0;
int ans = 0;
if(k <= S){
ans += val[x][k]; int res = (dep[x] - dep[y]) % k;
ans -= val[!res ? y : Up(y, k - res)][k];
}
else while(x && dep[x] > dep[y]) ans += w[x], x = Up(x, k);
return ans;
}
void Qu(int x, int y, int k){
int lca = LCA(x, y), res = (dep[x] + dep[y] - 2 * dep[lca]) % k;
int ans = Solve(x, lca, k);
if(res) ans += w[y], y = Up(y, res);
ans += Solve(y, fa[lca][0], k);
printf("%d\n", ans);
}
int main(){
n = read(); S = sqrt(n);
for(int i=1; i<=n; i++) w[i] = read();
for(int i=1; i<n; i++){
int x = read(), y = read();
add(x, y); add(y, x);
} dep[1] = 1; dfs(1, 0);
dfs2(1, 0);
for(int i=1; i<=n; i++) a[i] = read();
for(int i=1; i<n; i++) k[i] = read();
for(int i=1; i<n; i++) Qu(a[i], a[i+1], k[i]);
return 0;
}
ai < 30000 ? 要在 ai 上做文章
首先需要先到 n ^ 2 的暴力
l, r 表示i的左右有多少个为 j 的数
谁看得出来分块啊 ! 管它呢, 既然要分就分呗, 设块的大小为 S
对于i, j, k三者在三个不同块, 我们可以把 l, r 用FFT 卷起来, 然后枚举 j 的块的每一个 aj 询问贡献
复杂度
对于不是上述的情况, 我们可以在每个块中枚举i, j, 然后通过l, r统计答案
复杂度
#include<bits/stdc++.h>
#define N 800050
using namespace std;
typedef long long ll;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
int l[N], r[N], n, a[N], mx;
int pos[N], L[N], R[N]; ll ans;
const double PI = acos(-1.0);
struct Node{
double x, y;
Node(double _x = 0, double _y = 0){ x = _x, y = _y;}
Node operator + (const Node &a){ return Node(x + a.x, y + a.y);}
Node operator - (const Node &a){ return Node(x - a.x, y - a.y);}
Node operator * (const Node &a){ return Node(x * a.x - y * a.y, x * a.y + y * a.x);}
}A[N], B[N];
int up, bit, rev[N];
void Init(int len){ up = 1, bit = 0;
while(up <= len) up <<= 1, bit++;
for(int i=0; i<up; i++) rev[i] = (rev[i>>1]>>1) | ((i&1) << (bit-1));
}
void FFT(Node *a, int inv){
for(int i=0; i<up; i++) if(i < rev[i]) swap(a[i], a[rev[i]]);
for(int i=1; i<up; i<<=1){
Node wn(cos(PI/i), inv * sin(PI/i));
for(int j=0; j<up; j+=(i<<1)){
Node w(1, 0);
for(int k=0; k<i; k++, w=w*wn){
Node x = a[k+j], y = w * a[k+j+i];
a[k+j] = x + y; a[k+j+i] = x - y;
}
}
}
}
int lg[N];
int main(){
scanf("%d", &n);
for(int i=2; i<=n; i++) lg[i] = lg[i/2] + 1;
for(int i=1; i<=n; i++) a[i] = read(), mx = max(mx, a[i]);
int S = sqrt(n * lg[n]), res = 0;
for(int i=1; i<=n; i++) pos[i] = (i - 1) / S;
for(int i=1; i<=n; i+=S) L[++res] = i, R[res] = i + S - 1; R[res] = n;
for(int i=1; i<=n; i++) r[a[i]]++;
for(int i=1; i<=res; i++){
for(int j=L[i]; j<=R[i]; j++) r[a[j]]--;
for(int j=L[i]; j<=R[i]; j++){
for(int k=j+1; k<=R[i]; k++){
int x = 2 * a[j] - a[k]; if(x >= 0) ans += l[x];
x = 2 * a[k] - a[j]; if(x >= 0) ans += r[x];
} l[a[j]]++;
}
}
memset(l, 0, sizeof(l));
memset(r, 0, sizeof(r));
for(int i=1; i<=n; i++) r[a[i]]++;
for(int i=1; i<=res; i++){
for(int j=L[i]; j<=R[i]; j++) r[a[j]]--;
Init(mx << 1);
for(int j=0; j<up; j++) A[j] = B[j] = Node(0, 0);
for(int j=0; j<=mx; j++) A[j] = Node(l[j], 0);
for(int j=0; j<=mx; j++) B[j] = Node(r[j], 0);
FFT(A, 1); FFT(B, 1);
for(int j=0; j<up; j++) A[j] = A[j] * B[j];
FFT(A, -1);
for(int j=L[i]; j<=R[i]; j++){
int val = int(A[a[j] * 2].x / up + 0.5); ans += val;
}
for(int j=L[i]; j<=R[i]; j++) l[a[j]]++;
}
printf("%lld", ans); return 0;
}
分块的思想是真的巧妙. 例如第二题, 当 k 大了, 次数就少了, 当 k 小了, 就可以让我们预处理了
现实生活中, 学习生活中亦是如此, 当我们把我们的任务找到一个恰如其分的分界线, 不同的分类用不同的方法, 看似繁琐的问题也就迎刃而解了. 渐渐得, 你会发现, 根号真是一种美丽的复杂度