利用分块的思想解决的问题

BZOJ2741 L

首先有个 m * n logn 的做法, 就是暴力枚举右端点, 然后可持久化trie来查, 考虑如何优化

n 那么小是不是可以预处理一些东西, 我们又想到了分块

类似分块的套路, 可以预处理 f[i][j] , 表示第i块到第j块的答案

综合空间以及时间的考虑, 我们发现可以直接处理 f[i][j] 表示 第i块的起点到 j 的答案

f[i][j]=max(f[i][j-1], Query(L[i] -- j-1))

然后询问可以直接查, 剩下的 根号n个暴力查, 复杂度 n\sqrt(n)logn

#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;
}

P3591 [POI2015]ODW

k > 根号 n 直接暴力跳, 因为跳的次数不会超过 n / k

k < 根号 n 暴力预处理, val[u][k] 表示从u开始一直k步k步往上跳的答案, 像前缀和一样 dfs 一遍就可以了

因为跳需要倍增, 所以复杂度 n\sqrt(n)logn

#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;
}

[WOJ4418] [BZOJ3509] COUNTARI

ai < 30000 ? 要在 ai 上做文章

首先需要先到 n ^ 2 的暴力  ans=\sum_{i=1}^n\sum_{j=0}^{mx}l[j]*r[a_i*2-j]

l, r 表示i的左右有多少个为 j 的数

谁看得出来分块啊 ! 管它呢, 既然要分就分呗, 设块的大小为 S

对于i, j, k三者在三个不同块, 我们可以把 l, r 用FFT 卷起来, 然后枚举 j 的块的每一个 aj 询问贡献

复杂度  \frac{n}{S}*n*logn

对于不是上述的情况, 我们可以在每个块中枚举i, j, 然后通过l, r统计答案

复杂度 S^2*\frac{n}{S}=n*S

S=\sqrt{nlogn}\rightarrow O(n\sqrt{nlogn})

#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 小了, 就可以让我们预处理了

现实生活中, 学习生活中亦是如此, 当我们把我们的任务找到一个恰如其分的分界线, 不同的分类用不同的方法, 看似繁琐的问题也就迎刃而解了. 渐渐得, 你会发现, 根号真是一种美丽的复杂度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值