分块一:
给出一个长为n的数列,以及n个操作,操作涉及区间加法,单点查值。
普通分块水过
#include
#include
#include
#include
using namespace std;
int n, a[100100], len,tag[100100], posit[100100];
void change(int l, int r, int c) {
if (posit[l]==posit[r]) {
for (int i = l; i <= r; i++) {
a[i] += c;
}
return;
}
for(int i=l; i<=posit[l]*len; i++) a[i] += c;
for(int i=posit[l] + 1; i<= posit[r]-1; i++) tag[i] += c;
for(int i=(posit[r] - 1)*len +1;i <= r; i++) a[i] += c;
}
int main() {
scanf("%d", &n);
len = sqrt(n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
posit[i] = (i - 1) / len + 1;
}
int opt, l, r, c;
while (n--) {
scanf("%d%d%d%d", &opt, &l, &r, &c);
if (!opt) {
change(l,r,c);
} else {
printf("%d\n",a[r]+tag[posit[r]]);
}
}
return 0;
}
分块二:
给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的元素个数。
块内sort,lower_bound,加一个数组,辅助残块sort,因为原序列不能变
#include
#include
#include
#include
#include
#define int long long
#define MAXN 100000
using namespace std;
int n,opt,a[MAXN],b[MAXN],pos[MAXN],tag[MAXN],len;
void change(int l,int r,int c)
{
if(pos[l]==pos[r])
{
for(int i=l;i<=r;i++)a[i]+=c;
for(int i=(pos[l]-1)*len+1;i<=min(pos[l]*len,n);i++)b[i]=a[i];
sort(b+(pos[l]-1)*len+1,b+1+min(n,pos[l]*len));
return;
}
for(int i=l;i<=pos[l]*len;i++)a[i]+=c;
for(int i=(pos[l]-1)*len+1;i<=pos[l]*len;i++)b[i]=a[i];
sort(b+(pos[l]-1)*len+1,b+1+pos[l]*len);
for(int i=pos[l]+1;i<=pos[r]-1;i++)tag[i]+=c;
for(int i=(pos[r]-1)*len+1;i<=r;i++)a[i]+=c;
for(int i=(pos[r]-1)*len+1;i<=min(pos[r]*len,n);i++)b[i]=a[i];
sort(b+(pos[r]-1)*len+1,b+1+min(pos[r]*len,n));
}
int query(int l,int r,int c)
{
int ans=0;
if(pos[l]==pos[r])
{
for(int i=l;i<=r;i++)
{
if(a[i]+tag[pos[i]]
分块三:
给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的前驱(比其小的最大元素)。
块内sort加lower_bound查询
#include
#include
#include
#include
#define N 500500
using namespace std;
inline long long read() {
int x = 0, f = 1;
char s = getchar();
while (s < '0' || s > '9') {
if (s == '-')
f = -1;
s = getchar();
}
while (s >= '0' && s <= '9') {
x = x * 10 + s - '0';
s = getchar();
}
return x * f;
}
int n, len;
int a[N], b[N];
int tag[N], lz[N];
int L[N], R[N];
inline void change(int l, int r, int c) {
if (tag[l] == tag[r]) {
for (int i = l; i <= r; i++) a[i] += c;
for (int i = L[tag[l]]; i <= R[tag[r]]; i++) b[i] = a[i];
sort(b + L[tag[l]], b + R[tag[l]] + 1);
return;
}
for (int i = l; i <= R[tag[l]]; i++) a[i] += c;
for (int i = L[tag[l]]; i <= R[tag[l]]; i++) b[i] = a[i];
sort(b + L[tag[l]], b + R[tag[l]] + 1);
for (int i = tag[l] + 1; i < tag[r]; i++) lz[i] += c;
for (int i = L[tag[r]]; i <= r; i++) a[i] += c;
for (int i = L[tag[r]]; i <= R[tag[r]]; i++) b[i] = a[i];
sort(b + L[tag[r]], b + R[tag[r]] + 1);
}
inline int ask(int l, int r, int c) {
int ans = -1;
if (tag[l] == tag[r]) {
for (int i = l; i <= r; i++)
if (a[i] + lz[tag[i]] < c)
ans = max(ans, a[i] + lz[tag[i]]);
return ans;
}
for (int i = l; i <= R[tag[l]]; i++)
if (a[i] + lz[tag[i]] < c)
ans = max(ans, a[i] + lz[tag[i]]);
for (int i = tag[l] + 1; i < tag[r]; i++) {
int k = lower_bound(b + L[i], b + R[i] + 1, c - lz[i]) - b;
if (k != L[i])
if (b[k - 1] + lz[i] < c)
ans = max(ans, b[k - 1] + lz[i]);
}
for (int i = L[tag[r]]; i <= r; i++)
if (a[i] + lz[tag[i]] < c)
ans = max(ans, a[i] + lz[tag[i]]);
return ans;
}
int main() {
n = read();
int len = sqrt(n);
for (int i = 1; i <= n; i++) tag[i] = (i - 1) / len + 1;
for (int i = 1; i <= tag[n]; i++) L[i] = R[i - 1] + 1, R[i] = min(n, L[i] + len - 1);
for (int i = 1; i <= n; i++) a[i] = read(), b[i] = a[i];
for (int i = 1; i <= tag[n]; i++) sort(b + L[i], b + R[i] + 1);
for (int i = 1; i <= n; i++) {
int opt = read(), l = read(), r = read(), c = read();
if (opt == 0)
change(l, r, c);
else
printf("%d\n", ask(l, r, c));
}
return 0;
}
分块四:
给出一个长为n的数列,以及n个操作,操作涉及区间加法,区间求和。
打标记,其余暴力
#include
#include
#include
#include
#include
#define MAXN 1000010
#define int long long
int a[MAXN],tag[MAXN],le[MAXN],ri[MAXN],sum[MAXN],val[MAXN],pos[MAXN],len;
void change(int l,int r,int c)
{
if(pos[l]==pos[r])
{
for(int i=l;i<=r;i++)
{
a[i]+=c;val[pos[i]]+=c;
}
return;
}
for(int i=l;i<=ri[pos[l]];i++) a[i]+=c , val[pos[i]]+=c;
for(int i=pos[l]+1;i <= pos[r]-1 ; i++)tag[i]+=c;
for(int i=le[pos[r]];i<=r;i++) a[i]+=c , val[pos[i]]+=c;
}
int query(int l,int r,int c)
{
int mod=c+1,ans=0;
if(pos[l]==pos[r])
{
for(int i=l;i<=r;i++)ans+=(a[i]+tag[pos[i]]),ans%=mod;
return ans;
}
for(int i=l;i<=ri[pos[l]];i++)ans+=(a[i]+tag[pos[i]]),ans%=mod;
for(int i=pos[l]+1;i<=pos[r]-1;i++)
{
ans+=(val[i]+tag[i]*len);
ans%=mod;
}
for(int i=le[pos[r]];i<=r;i++)ans+=(a[i]+tag[pos[i]]),ans%=mod;
return ans;
}
signed main()
{
int n;
scanf("%lld",&n);
len=sqrt(n);
for(int i=1;i<=n;i++)
{
pos[i]=(i-1)/len+1;
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=pos[n];i++)
{
le[i]=(i-1)*len+1;
ri[i]=i*len;
val[i]=sum[ri[i]]-sum[le[i]-1];
}
for(int i=1,opt,l,r,c;i<=n;i++)
{
scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
if(opt==0)change(l,r,c);
if(opt==1)
{
printf("%lld\n",query(l,r,c));
}
}
return 0;
}
分块五:
给出一个长为n的数列,以及n个操作,操作涉及区间开方,区间求和。
线段树,标记维护区间是否都为一
#include
#include
#include
#include
#define MAXN 5100000
#define int long long
using namespace std;
long long tr[MAXN*4],n,a[MAXN*2];
bool vis[MAXN*4];
void updata(int k)
{
tr[k]=tr[k<<1]+tr[k<<1|1];
if(vis[k<<1]==1 && vis[k<<1|1]==1)vis[k]=1;
return;
}
void change(int k,int l,int r,int x,int y)
{
if(vis[k])return;
if(l>=x && r<=y && l==r)
{
tr[k]=sqrt(tr[k]);
if(tr[k]==1||tr[k]==0)vis[k]=1;
return;
}
int mid=(l+r)>>1;
if(x<=mid)change(k<<1,l,mid,x,y);
if(y>mid)change(k<<1|1,mid+1,r,x,y);
updata(k);
}
int ask(int k,int l,int r,int x,int y)
{
if(l>=x && r<=y)return tr[k];
int mid=(l+r)>>1,ans=0;
if(x<=mid)ans+=ask(k<<1,l,mid,x,y);
if(y>mid)ans+=ask(k<<1|1,mid+1,r,x,y);
return ans;
}
void build(int k,int l,int r)
{
if(l==r)
{
tr[k]=a[l];
if(a[l]==1||a[l]==0)vis[k]=1;
return ;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
updata(k);
}
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
build(1,1,n);
for(int i=1,opt,l,c,r;i<=n;i++)
{
scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
if(l>r)swap(l,r);
if(opt==0)
{
change(1,1,n,l,r);
}
else
{
printf("%lld\n",ask(1,1,n,l,r));
}
}
return 0;
}
分块六:
给出一个长为n的数列,以及n个操作,操作涉及单点插入,单点询问,数据随机生成。
开一个vector 插入,pair ,
先说随机数据的情况
之前提到过,如果我们块内用数组以外的数据结构,能够支持其它不一样的操作,比如此题每块内可以放一个动态的数组,每次插入时先找到位置所在的块,再暴力插入,把块内的其它元素直接向后移动一位,当然用链表也是可以的。
查询的时候类似,复杂度分析略。
但是这样做有个问题,如果数据不随机怎么办?
如果先在一个块有大量单点插入,这个块的大小会大大超过√n,那块内的暴力就没有复杂度保证了。
还需要引入一个操作:重新分块(重构)
每根号n次插入后,重新把数列平均分一下块,重构需要的复杂度为O(n),重构的次数为√n,所以重构的复杂度没有问题,而且保证了每个块的大小相对均衡。
当然,也可以当某个块过大时重构,或者只把这个块分成两半。
#include
#include
#include
#include
#include
#define N 200000
using namespace std;
int pos[N],len,le[N],ri[N],n,a[N],st[N],m;
vector ve[N];
pair query(int b)
{
int x=1;
while(b>ve[x].size())
{
b-=ve[x].size();
x++;
}
return make_pair(x,b-1);
}
void rebuild()
{
int top=1;
for(int i=1;i<=m;i++)
{
for(vector ::iterator j=ve[i].begin();j!=ve[i].end();j++)
st[++top]=*j;
ve[i].clear();
}
int le=sqrt(top);
for(int i=1;i<=top;i++)ve[(i-1)/le+1].push_back(st[i]);
m=(top-1)/le+1;
}
void insert(int a,int b)
{
pair t=query(a);
ve[t.first].insert( ve[t.first].begin() + t.second ,b);
// if(ve[t.first].size()>20*len) rebuild();
}
int main()
{
// freopen("a9.in","r",stdin);
// freopen("ans.out","w",stdout);
scanf("%d",&n);
len=sqrt(n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) ve[(i-1)/len+1].push_back(a[i]);
int m=(n-1)/len+1;
for(int i=1,opt,x,y,c;i<=n;i++)
{
scanf("%d%d%d%d",&opt,&x,&y,&c);
if(opt==0)
{
insert(x,y);
}
if(opt==1)
{
pair t = query(y);
printf("%d\n",ve[t.first][t.second]);
}
}
return 0;
}
分块七:
给出一个长为n的数列,以及n个操作,操作涉及区间乘法,区间加法,单点询问。
很显然,如果只有区间乘法,和分块入门 1 的做法没有本质区别,但要思考如何同时维护两种标记。
我们让乘法标记的优先级高于加法(如果反过来的话,新的加法标记无法处理)
若当前的一个块乘以m1后加上a1,这时进行一个乘m2的操作,则原来的标记变成m1*m2,a1*m2
若当前的一个块乘以m1后加上a1,这时进行一个加a2的操作,则原来的标记变成m1,a1+a2
#include
分块八:
给出一个长为n的数列,以及n个操作,操作涉及区间询问等于一个数c的元素,并将这个区间的所有元素改为c。
区间修改没有什么难度,这题难在区间查询比较奇怪,因为权值种类比较多,似乎没有什么好的维护方法。
模拟一些数据可以发现,询问后一整段都会被修改,几次询问后数列可能只剩下几段不同的区间了。
我们思考这样一个暴力,还是分块,维护每个分块是否只有一种权值,区间操作的时候,对于同权值的一个块就O(1)统计答案,否则暴力统计答案,并修改标记,不完整的块也暴力。
这样看似最差情况每次都会耗费O(n)的时间,但其实可以这样分析:
假设初始序列都是同一个值,那么查询是O(√n),如果这时进行一个区间操作,它最多破坏首尾2个块的标记,所以只能使后面的询问至多多2个块的暴力时间,所以均摊每次操作复杂度还是O(√n)。
换句话说,要想让一个操作耗费O(n)的时间,要先花费√n个操作对数列进行修改。
初始序列不同值,经过类似分析后,就可以放心的暴力啦。
#include
#include
#include
#include
using namespace std;
const int N = 1000000;
int pos[N],le[N],ri[N],cnt,n,a[N],tag[N],len;
void reset(int x)
{
if(tag[x]==-1)return;
else
{
for(int i=le[x];i<=ri[x];i++)
a[i]=tag[x];
}
tag[x]=-1;
}
void change(int l,int r,int c)
{
int ans=0;
reset(pos[l]);
for(int i=l;i<=min(ri[pos[l]],r);i++)
{
if(a[i]!=c)a[i]=c;
else ans++;
}
if(pos[l]!=pos[r])
{
reset(pos[r]);
for(int i=le[pos[r]];i<=r;i++)
{
if(a[i]!=c)a[i]=c;
else ans++;
}
}
for(int i=pos[l]+1;i<=pos[r]-1;i++)
{
if(tag[i]!=-1)
{
if(tag[i]!=c)tag[i]=c;
else ans+=len;
}
else
{
for(int j=le[i];j<=ri[i];j++)
{
if(a[j]!=c)a[j]=c;
else ans++;
tag[i]=c;
}
}
}
printf("%d\n",ans);
}
int main()
{
scanf("%d",&n);
len=sqrt(n);
memset(tag,-1,sizeof(tag));
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)pos[i]=(i-1)/len+1;
for(int i=1;i<=pos[n];i++)
{
le[i]=(i-1)*len+1;
ri[i]=min(n,i*len);
}
for(int i=1,l,r,c;i<=n;i++)
{
scanf("%d%d%d",&l,&r,&c);
change(l,r,c);
}
return 0;
}
分块九:
给出一个长为n的数列,以及n个操作,操作涉及询问区间的最小众数。 不带修改,要是带呢?
#include
分块--好爽