简单分块
分析
分成若干块,每块预处理,暴力合并
数据范围一般有点特别
预处理有两类方法:
- 预处理连续的块之间的答案
- 预处理出单独块的答案,然后\(O(\frac{n}{S})\)大力合并
例题1
采用分块,分完后暴力求出连续的块的答案\(O(n\sqrt n)\),以及块中元素的前缀和\(O(C\sqrt n)\)
对于每次询问,加入完整块的答案,暴力搞不完整块,时间复杂度是\(O(n\sqrt n)\)
WA了一次:预处理是下标写错了
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=320;
int n,m,c,S,bl[N],s[M][N],f[M][M],t[N],a[N];
int main() {
scanf("%d%d%d",&n,&c,&m);
S=sqrt(n);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
bl[i]=(i-1)/S+1;
}
for(int i=1;i<=n;i+=S) {
for(int j=1;j<=c;j++) s[bl[i]][j]=s[bl[i]-1][j];
for(int j=i;j<=min(i+S-1,n);j++) {
s[bl[i]][a[j]]++;
}
}
int ans=0;
for(int i=1;i<=n;i+=S) {
ans=0;
for(int j=bl[i];j<=bl[n];j++) {
for(int k=S*(j-1)+1;k<=min(S*j,n);k++) {
if(t[a[k]]>0&&t[a[k]]%2==0) {
ans--;
}
t[a[k]]++;
if(t[a[k]]%2==0) ans++;
}
f[bl[i]][j]=ans;
}
memset(t,0,sizeof(t));
}
ans=0;
while(m--) {
int l,r; scanf("%d%d",&l,&r),l=(l+ans)%n+1,r=(r+ans)%n+1;
if(l>r)swap(l,r);
int t1=bl[l],t2=bl[r];
if(t2-t1<=1) {
ans=0;
for(int j=l;j<=r;j++) {
if(t[a[j]]>0&&t[a[j]]%2==0) {
ans--;
}
t[a[j]]++;
if(t[a[j]]%2==0) {
ans++;
}
}
printf("%d\n",ans);
for(int j=l;j<=r;j++) {
t[a[j]]--;
}
continue;
}
ans=f[t1+1][t2-1];
for(int i=l;i<=t1*S;i++) {
if(!t[a[i]]) {
t[a[i]]=s[t2-1][a[i]]-s[t1][a[i]];
}
if(t[a[i]]>0&&t[a[i]]%2==0) {
ans--;
}
t[a[i]]++;
if(t[a[i]]%2==0) ans++;
}
for(int i=(t2-1)*S+1;i<=r;i++) {
if(!t[a[i]]) {
t[a[i]]=s[t2-1][a[i]]-s[t1][a[i]];
}
if(t[a[i]]>0&&t[a[i]]%2==0) {
ans--;
}
t[a[i]]++;
if(t[a[i]]%2==0) ans++;
}
printf("%d\n",ans);
for(int i=l;i<=t1*S;i++) {
t[a[i]]=0;
}
for(int i=(t2-1)*S+1;i<=r;i++) {
t[a[i]]=0;
}
}
return 0;
}
例题2
糟糕,是心动的感觉,是以前做过的比赛里的压轴(zhou\)题
还记得当时写完暴力后不知所措的样子
还记得黄队讲的时候被dui了无耐地表示没写过
设区间答案为\(f[i,j,x0]\)(太妙了)
显然\(x0\)越大答案越优,且\(x0\)到达一定峰值后答案将不再变化
所以,我们引进函数\(g[i,j]\)表示\(f[i,j,INF]\),即到达天花板时的答案,\(s[i,j]\)表示\(\sum_{k=i}^{j}D[k]\)
如果\(x0\)能到达天花板,则\(f[i,j,x0]\)=\(g[i,j]\),此时\(g[i,j]\leq s[i,j]+x0\)
但如果\(x0\)不能,,则\(f[i,j,x0]\)=\(s[i,j]+x0\),此时\(g[i,j]\geq s[i,j]+x0\)
考虑优化:如果\(g[i1,j1]\leq g[i2,j2],s[i1,j1]\leq s[i2,j2]\) 则\((i1,j1)\)没用
所以将\(g\)从小到大排序后,\(s\)也应该是有序的,且从大到小

可以发现\(f\)是一个单峰函数,且在\(g,s\)大小关系变化处取到极值,可以二分
所以对每块维护里面所有的\(g,s\)对
但块和块之间呢,显然不能预处理了,采用方法2
对于每块只有5种情况
- 全在块内(预处理)
- 走到块为止(记块前的最大值mx,预处理前缀)
- 从块出发(预处理后缀,求出最大值)
- 经过块(预处理块的答案,暴力给mx)
- 不经过块()
接下来是简单~的代码实现
WA了1次:预处理后缀时真就从后往前预处理,像个憨憨(顺序和答案有关啊)
TLE了1次:直接用vector传递变量(时间复杂度是vector),像个憨憨,前面加&就行了
#include <bits/stdc++.h>
#define ll long long
const int INF = 1e9;
using namespace std;
const int N = 5e4 + 5, M = 230;
int n, S, bl[N], b[M], a[N], L[N];
ll s[N];
struct A {
int x, y;
};
vector<A> V, f[M], pre[M], suf[M];
inline bool cmp(A i, A j) { return i.x < j.x || i.x == j.x && i.y < j.y; }
int ask(vector<A> &t, int x) {
int l = 0, r = t.size() - 1, ret = 0;
while (l <= r) {
int mid = l + r >> 1;
if (t[mid].y + x - t[mid].x > 0) {
ret = mid, l = mid + 1;
} else
r = mid - 1;
}
if (ret == t.size() - 1) {
return min(t[ret].x, t[ret].y + x);
}
return max(min(t[ret].x, t[ret].y + x), min(t[ret + 1].x, t[ret + 1].y + x));
}
void bld(vector<A>& t) {
sort(V.begin(), V.end(), cmp);
int num = V.size();
t.push_back(V[0]);
int sz = 1;
for (int j = 1; j < num; j++) {
while (sz && t[sz - 1].y <= V[j].y) {
t.pop_back();
sz--;
}
t.push_back(V[j]), sz++;
}
}
int main() {
int T;
scanf("%d%d", &n, &T), S = sqrt(n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]), s[i] = s[i - 1] + a[i];
bl[i] = (i - 1) / S + 1;
}
for (int i = 1; i <= n; i++) {
scanf("%d", &L[i]);
}
for (int i = 1; i <= n; i += S) {
V.clear();
for (int j = i; j <= min(i + S - 1, n); j++) {
int t = INF;
for (int k = j; k <= min(i + S - 1, n); k++) {
t = min(t + a[k], L[k]);
V.push_back((A){ t, min((ll)INF, s[k] - s[j - 1]) });
}
}
bld(f[bl[i]]);
V.clear();
int t = INF;
for (int j = i; j <= min(i + S - 1, n); j++) {
t = min(t + a[j], L[j]);
V.push_back((A){ t, min((ll)INF, s[j] - s[i - 1]) });
}
b[bl[i]] = t;
bld(pre[bl[i]]);
V.clear();
for (int j = i; j <= min(i + S - 1, n); j++) {
t = INF;
for (int k = j; k <= min(i + S - 1, n); k++) {
t = min(t + a[k], L[k]);
}
V.push_back((A){ t, min((ll)INF, s[min(i + S - 1, n)] - s[j - 1]) });
}
bld(suf[bl[i]]);
}
while (T--) {
int l, r, x;
scanf("%d%d%d", &l, &r, &x);
int t1 = bl[l], t2 = bl[r];
if (t1 == t2) {
int ans = 0, t = x;
for (int i = l; i <= r; i++) {
t = min(t + a[i], L[i]);
t = max(t, x);
ans = max(ans, t);
}
printf("%d\n", ans);
continue;
}
int ans = 0, t = x, mx = 0;
for (int i = l; i <= t1 * S; i++) {
t = min(t + a[i], L[i]);
t = max(t, x);
ans = max(ans, t);
}
mx = t;
for (int i = t1 + 1; i <= t2 - 1; i++) {
ans = max(ans, ask(f[i], x));
ans = max(ans, ask(pre[i], mx));
t = min((ll)b[i], min((ll)INF, s[i * S] - s[(i - 1) * S] + mx));
ans = max(ans, t);
mx = ask(suf[i], x);
mx = max(mx, t);
mx = max(mx, x);
}
t = mx;
for (int i = (t2 - 1) * S + 1; i <= r; i++) {
t = min(t + a[i], L[i]);
t = max(t, x);
ans = max(ans, t);
}
printf("%d\n", ans);
}
return 0;
}
例题3
显然也是分块
预处理出前缀和,块和块之间的颜色小于K的答案
时间复杂度是\(O((\frac{n}{S})^2n+nS)\),取\(S=n^{\frac{2}{3}}\)即可

分块
浙公网安备 33010602011771号