艱苦地刷了4天半的分塊
深感分塊是一個非常巧(暴)妙(力) 的算法
如果有覺得hzwer的代碼太奇妙(看不懂)的推薦一下機房大佬的通俗易懂的代碼:
http://www.cnblogs.com/CHerish_OI/category/1176577.html(此處手動艾特cherish_oi同學)
http://hzwer.com/8053.html
loj#6277. 數列分塊入門 1
區間加法,單點查詢
基礎的東西
數學老師曰:基本知識不熟悉,難題不會做!
分塊 每個塊的大小都是sqrt(n)
n=12 N=sqrt(12) 向下取整 3
第一塊:1~3 第二塊:4~6 第三塊:7~9 第四塊: 10~12
屬於第幾塊pos[i]=(i-1)/N+1;
如果是完整的塊就直接更新到標記裏
不完整的塊就直接暴力更新就ok
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int m,pos[51000];
int s[51000],tag[51000];
void change(int a,int b,int c)
{
for (int i=a;i<=min(b,pos[a]*m);i++) s[i]+=c;//如果b在a的塊內就更新到b 否則更新到a的塊的結尾
if (pos[a]!=pos[b])
{
for (int i=(pos[b]-1)*m+1;i<=b;i++) s[i]+=c; //如果ab不同塊 更新b的塊內的開頭到b
}
for (int i=pos[a]+1;i<=pos[b]-1;i++) tag[i]+=c;
}
int main()
{
int n;
scanf("%d",&n);
m=sqrt(n);
for (int i=1;i<=n;i++) pos[i]=(i-1)/m+1;
for (int i=1;i<=n;i++) scanf("%d",&s[i]);
for (int i=1;i<=n;i++)
{
int x,a,b,c;
scanf("%d%d%d%d",&x,&a,&b,&c);
if (x==0)
{
change(a,b,c);
}
else if (x==1)
{
printf("%d\n",tag[pos[b]]+s[b]);
}
}
return 0;
}
loj#6278. 數列分塊入門 2
區間加法,查詢比x小的個數
排序維護一下塊內的遞增性
完整的塊可以直接用lower_bound返回一下第一個大於等於x的位置
不完整的塊還是直接暴力
非常不爭氣的寫了STL
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
#include <cmath>
using namespace std;
int n,N;
int pos[51000],s[51000],tag[51000];
vector<int> v[510];
void update(int x)
{
v[x].clear();
for (int i=(x-1)*N+1;i<=min(x*N,n);i++)//有可能這是最後一個塊 min一下
v[x].push_back(s[i]);//push_back:壓進容器尾部
sort(v[x].begin(),v[x].end());//從容器頭到容器尾排序
}
void add(int x,int y,int c)
{
for (int i=x;i<=min(pos[x]*N,y);i++)//x,y有可能同塊 否則更新完x塊剩下整個快
s[i]+=c;
update(pos[x]);
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=y;i++) //更新y所在的不完整塊
s[i]+=c;
update(pos[y]);
}
for (int i=pos[x]+1;i<pos[y];i++) tag[i]+=c;
}
int getsum(int x,int y,int c)//x~y中統計小於c個數
{
int ans=0;
for (int i=x;i<=min(pos[x]*N,y);i++)
if (s[i]+tag[pos[x]]<c) ans++; //x塊
if (pos[x]!=pos[y])//y
{
for (int i=((pos[y]-1)*N+1);i<=y;i++) if (s[i]+tag[pos[y]]<c) ans++;
}
for (int i=pos[x]+1;i<pos[y];i++)
{
int t=c-tag[i];
ans+=lower_bound(v[i].begin(),v[i].end(),t)-v[i].begin();//lower_bound:返回x的後繼(第一個比x大於等於的位置)(v內有序)
}
return ans;
}
int main()
{
scanf("%d",&n);
N=sqrt(n);
for (int i=1;i<=n;i++) scanf("%d",&s[i]);
for (int i=1;i<=n;i++)
{
pos[i]=(i-1)/N+1;
v[pos[i]].push_back(s[i]);
}
for (int i=1;i<=pos[n];i++)
sort(v[i].begin(),v[i].end());
for (int i=1;i<=n;i++)
{
int u,x,y,c;
scanf("%d%d%d%d",&u,&x,&y,&c);
if (u==0) add(x,y,c);
else if (u==1) printf("%d\n",getsum(x,y,c*c));
}
return 0;
}
loj#6279. 數列分塊入門 3
區間加法,求某個數的前驅
用set維護方便很多(其實相當於一顆splay把我覺得,手動艾特lynkin同學用的二分查找前驅過了)
依舊是完整的塊直接lower_bound
不完整的塊還是繼續暴力
不過要注意set的使用方法 刪掉再進行暴力修改 再加回去
依舊不爭氣地繼續用了STL
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <set>
using namespace std;
set<int> tr[1100];//set相當於一顆樹
int s[110000],tag[110000],pos[110000];
int n,N;
void add(int x,int y,int c)
{
for (int i=x;i<=min(pos[x]*N,y);i++)
{
tr[pos[x]].erase(s[i]);
s[i]+=c;
tr[pos[x]].insert(s[i]);
}
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=y;i++)
{
tr[pos[y]].erase(s[i]);
s[i]+=c;
tr[pos[y]].insert(s[i]);
}
}
for (int i=pos[x]+1;i<pos[y];i++) tag[i]+=c;
}
int findqianqu(int x,int y,int c)
{
int ans=-1;
for (int i=x;i<=min(pos[x]*N,y);i++)
{
int sum=s[i]+tag[pos[x]];
if (sum<c) ans=max(ans,sum);
}
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=y;i++)
{
int sum=s[i]+tag[pos[y]];
if (sum<c) ans=max(ans,sum);
}
}
for (int i=pos[x]+1;i<pos[y];i++)
{
int t=c-tag[i];
set<int>::iterator k=tr[i].lower_bound(t); //定義一個k的指針 一開始指向t的後繼
if (k==tr[i].begin()) continue;
k--;
ans=max(ans,*k+tag[i]);//加*代表的是k所指向的值
}
return ans;
}
int main()
{
scanf("%d",&n);N=sqrt(n);
for (int i=1;i<=n;i++) scanf("%d",&s[i]);
for (int i=1;i<=n;i++)
{
pos[i]=(i-1)/N+1;
tr[pos[i]].insert(s[i]);
}
for (int i=1;i<=n;i++)
{
int u,x,y,c;
scanf("%d%d%d%d",&u,&x,&y,&c);
if (u==0) add(x,y,c);
else if (u==1) printf("%d\n",findqianqu(x,y,c));
}
return 0;
}
loj#6280. 數列分塊入門 4
區間加法,區間求和
和分塊1是差不多的
順着思路用多一個數組來記錄每一個塊的總和就可以
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int n,N,pos[51000];
long long tag[51000],s[51000],a[51000]; //a:記錄塊內總和
void add(int x,int y,int c)
{
for (int i=x;i<=min(pos[x]*N,y);i++)
{
s[i]+=c;
a[pos[x]]+=c;
}
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=y;i++)
{
s[i]+=c;
a[pos[y]]+=c;
}
}
for (int i=pos[x]+1;i<pos[y];i++) tag[i]+=c;
}
long long getsum(int x,int y)
{
long long ans=0;
for (int i=x;i<=min(pos[x]*N,y);i++)
{
ans+=s[i]+tag[pos[x]];
}
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=y;i++)
{
ans+=s[i]+tag[pos[y]];
}
}
for (int i=pos[x]+1;i<pos[y];i++) ans+=a[i]+tag[i]*N;
return ans;
}
int main()
{
scanf("%d",&n);N=sqrt(n);
for (int i=1;i<=n;i++) pos[i]=(i-1)/N+1;
for (int i=1;i<=n;i++) {scanf("%d",&s[i]);a[pos[i]]+=s[i];}
for (int i=1;i<=n;i++)
{
int u,x,y,c;
scanf("%d%d%d%d",&u,&x,&y,&c);
if (u==0) add(x,y,c);
else if (u==1) printf("%lld\n",getsum(x,y)%(c+1));
}
return 0;
}
loj#6281. 數列分塊入門 5
區間開方,區間求和
其實不難發現就,即使是開方,操作量也不是很大
就拿最大值 2^31來說 開5次平方就已經小於2接近1了
所以當一個數已經小於等於1的時候 開方也沒有什麼意義了(向下取整不是0就是1 1^n=1 0^n=0)
同理,我們參照上面的思路開多一個數組標記這個塊內的數是不是全都小於等於1了,是的話直接不用更新就ok
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int pos[510000];
long long s[510000],a[510000];
bool v[510000]; //標記塊內是不是全都<=1
int n,N;
void solvesqrt(int x)
{
if (v[x]) return ;
v[x]=1;a[x]=0;
for (int i=(x-1)*N+1;i<=x*N;i++)
{
s[i]=sqrt(s[i]);
a[x]+=s[i];
if (s[i]>1) v[x]=false;
}
}
void add(int x,int y)
{
if (v[pos[x]]==0)
{
for (int i=x;i<=min(pos[x]*N,y);i++)
{
a[pos[x]]-=s[i];
s[i]=sqrt(s[i]);
a[pos[x]]+=s[i];
}
v[pos[x]]=true;
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++)
if (s[i]>1) {v[pos[x]]=false;break;}
}
if (pos[x]!=pos[y]&&v[pos[y]]==0)
{
for (int i=(pos[y]-1)*N+1;i<=y;i++)
{
a[pos[y]]-=s[i];
s[i]=sqrt(s[i]);
a[pos[y]]+=s[i];
}
v[pos[y]]=true;
for (int i=(pos[y]-1)*N+1;i<=pos[y]*N;i++)
if (s[i]>1) {v[pos[y]]=false;break;}
}
for (int i=pos[x]+1;i<pos[y];i++) solvesqrt(i);
}
long long getsum(int x,int y)
{
long long ans=0;
for (int i=x;i<=min(pos[x]*N,y);i++) ans+=s[i];
if (pos[x]!=pos[y])
for (int i=(pos[y]-1)*N+1;i<=y;i++) ans+=s[i];
for (int i=pos[x]+1;i<pos[y];i++) ans+=a[i];
return ans;
}
int main()
{
memset(v,false,sizeof(v));
scanf("%d",&n);N=sqrt(n);
for (int i=1;i<=n;i++) scanf("%lld",&s[i]);
for (int i=1;i<=n;i++)
{
pos[i]=(i-1)/N+1;
a[pos[i]]+=s[i];
}
for (int i=1;i<=n;i++)
{
int u,x,y,c;
scanf("%d%d%d%d",&u,&x,&y,&c);
if (x>y) swap(x,y);
if (u==0) add(x,y);
else if (u==1) printf("%lld\n",getsum(x,y));
}
return 0;
}
這裏順便插入一個 bzoj3211: 花神遊歷各國&上帝造題的七分鐘
如果不想寫線段樹的話可以寫分塊 其實就是分塊5原題
刷一刷雙經題
loj#6282. 數列分塊入門 6
單點插入,單點詢問
hzwer的代碼太高深看不懂……
於是寫了這個模樣的
用a[i][j]記錄第i塊的第j個數
len[i]記錄第i塊有多少個數
修改的時候直接暴力找到點應在的塊
暴力把r後面所有的往後挪
但是如果數據不隨機 有可能點會集中的插在某個塊當中,然後查詢的時候直接……
所以當一個塊的大小太大的時候就重新分一下塊 (也可以直接把大的那個塊拆成兩個)
先用b數組分好在倒到a裏面去
有沒有覺得很巧妙很暴力
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int a[1100][1100],b[1100][1100],len[1100]; //a,b:相當於兩個醬油瓶倒一倒 len:每一塊的長度
int n,N,np=0,m;//np:new point計數器 新增點數數量, m:新總長
void rebuild()
{
int x=0,k=m/N,ll=0;
if (m%N!=0) k++;
m+=N; np=0;
N=sqrt(m);
for (int i=1;i<=k;i++)
{
for (int j=1;j<=len[i];j++)
{
x++;
int kk=(x-1)/N+1;
b[kk][++ll]=a[i][j];
a[i][j]=0;
if (ll>=N) ll=0;
}
}
k=m/N;
if (m%N==0) k++;
for (int i=1;i<k;i++)
{
len[i]=N;
for (int j=1;j<=len[i];j++) a[i][j]=b[i][j];
}
if (m%N==0) len[k]=N;
else len[k]=m-N*N;
for (int j=1;j<=len[k];j++) a[k][j]=b[k][j];
}
int main()
{
scanf("%d",&n);N=sqrt(n); m=n;
for (int i=1;i<=n;i++)
{
int s;
scanf("%d",&s);
int k=(i-1)/N+1;
a[k][++len[k]]=s;
}
for (int i=1;i<=n;i++)
{
int u,l,r,c;
scanf("%d%d%d%d",&u,&l,&r,&c);
if (u==0)
{
np++;
int x=0,t;
for (t=1;t<=N;t++)
{
if (x+len[t]>=l) break;
else x+=len[t];
}
len[t]++;
l=l-x;
for (int i=len[t];i>l;i--) a[t][i]=a[t][i-1];
a[t][l]=r;
if (np>=N) rebuild();//如果大於N就重新分一次塊
}
else
{
int t,x=0;
for (t=1;t<=N;t++)
{
if (x+len[t]>=r) break;
else x+=len[t];
}
printf("%d\n",a[t][r-x]);
}
}
return 0;
}
loj#6283. 數列分塊入門 7
區間乘法,區間加法,區間查詢
其實和第二題並沒有什麼本質區別……
維護一個加法標記再維護一個乘法標記就ok
但是注意維護兩個標記的時候要麻煩一點
其實記住運用好小學知識——乘法分配率 就ok
taga(加法標記)tagc(乘法標記)
先加再乘:taga=taga*c——tagc*=c
先乘再加:taga+=c ——tagc=tagc
暴力更新不完整的塊的時候要先把之前整個塊的兩個標記放下去,再進行這一次的操作
還有記得初始化乘法標記不要寫0……
代碼寫的很醜勞煩大佬看的時候屏蔽一下衆多的%10007
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int pos[110000],s[110000],taga[110000],tagc[110000]; //taga:加法標記 tagc:乘法標記
int n,N;
void add(int x,int y,int c)
{
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++)
{
s[i]=(s[i]*tagc[pos[x]]%10007+taga[pos[x]]+10007)%10007;
if (x<=i&&i<=y) s[i]=(s[i]+c+10007)%10007;
}
tagc[pos[x]]=1;
taga[pos[x]]=0;
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=pos[y]*N;i++)
{
s[i]=(s[i]*tagc[pos[y]]%10007+taga[pos[y]]+10007)%10007;
if (i<=y) s[i]=(s[i]+c+10007)%10007;
}
tagc[pos[y]]=1;
taga[pos[y]]=0;
}
for (int i=pos[x]+1;i<pos[y];i++) taga[i]=(taga[i]+c+10007)%10007;
}
void cf(int x,int y,int c)
{
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++)
{
s[i]=(s[i]*tagc[pos[x]]%10007+taga[pos[x]]+10007)%10007;
if (x<=i&&i<=y) s[i]=(s[i]*c+10007)%10007;
}
tagc[pos[x]]=1;
taga[pos[x]]=0;
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=pos[y]*N;i++)
{
s[i]=(s[i]*tagc[pos[y]]%10007+taga[pos[y]]+10007)%10007;
if (i<=y) s[i]=(s[i]*c+10007)%10007;
}
tagc[pos[y]]=1;
taga[pos[y]]=0;
}
for (int i=pos[x]+1;i<pos[y];i++) {tagc[i]=(tagc[i]*c+10007)%10007;taga[i]=(taga[i]*c+10007)%10007;}
}
int main()
{
scanf("%d",&n);N=sqrt(n);
for (int i=1;i<=n;i++)
{
scanf("%d",&s[i]);
s[i]%=10007;
pos[i]=(i-1)/N+1;
taga[pos[i]]=0;tagc[pos[i]]=1;
}
for (int i=1;i<=n;i++)
{
int u,l,r,c;
scanf("%d%d%d%d",&u,&l,&r,&c);
c%=10007;
if (u==0) add(l,r,c);
else if (u==1) cf(l,r,c);
else if (u==2) printf("%d\n",(s[r]*tagc[pos[r]]+taga[pos[r]])%10007);
}
return 0;
}
loj#6284. 數列分塊入門 8
區間詢問,區間修改
繼續運用上面的思路用上各種各樣的標記就凹k
用一個數組記錄這一個塊內是否都是同一個數
是的話標記爲這個數,否則爲-1
查詢的時候分情況討論標記是否爲-1 、c、≠c&&≠ -1
-1直接暴力for一遍統計然後更新標記
≠ -1&&≠c 沒得詢問直接更新
==c ans+=N 不用詢問直接更新答案就好
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int pos[110000],a[110000],v[110000];
int n,N;
void solve(int x,int y,int c)
{
int ans=0;
if (pos[x]==pos[y])
{
if (v[pos[x]]==c) ans+=y-x+1;
else
{
if (v[pos[x]]!=-1)
{
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++) a[i]=v[pos[x]];
}
for (int i=x;i<=y;i++) if (a[i]==c) ans++; else a[i]=c;
bool bk=false;
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++) if (a[i]!=c) {bk=true;break;}
if (bk) v[pos[x]]=-1;
else v[pos[x]]=c;
}
}
else
{
if (v[pos[x]]==c) ans+=pos[x]*N-x+1;
else
{
if (v[pos[x]]!=-1)
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++) a[i]=v[pos[x]];
for (int i=x;i<=pos[x]*N;i++) if (a[i]==c) ans++; else a[i]=c;
bool bk=false;
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++) if (a[i]!=c) {bk=true;break;}
if (bk) v[pos[x]]=-1;
else v[pos[x]]=c;
}
if (v[pos[y]]==c) ans+=y-((pos[y]-1)*N+1)+1;
else
{
if (v[pos[y]]!=-1)
for (int i=(pos[y]-1)*N+1;i<=pos[y]*N;i++) a[i]=v[pos[y]];
for (int i=(pos[y]-1)*N+1;i<=y;i++) if (a[i]==c) ans++; else a[i]=c;
bool bk=false;
for (int i=(pos[y]-1)*N+1;i<=pos[y]*N;i++) if (a[i]!=c) {bk=true;break;}
if (bk) v[pos[y]]=-1;
else v[pos[y]]=c;
}
for (int i=pos[x]+1;i<pos[y];i++)
{
if (v[i]==c) ans+=N;
else
{
if (v[i]!=-1)
{
//for (int j=(i-1)*N+1;j<=i*N;j++) a[j]=c; 小心不要手抽寫了這句小心會t!
v[i]=c;
}
else
{
for (int j=(i-1)*N+1;j<=i*N;j++) if (a[j]==c) ans++; else a[j]=c;
bool bk=false;
for (int j=(i-1)*N+1;j<=i*N;j++) if (a[j]!=c) {bk=true;break;}
if (bk) v[i]=-1;
else v[i]=c;
}
}
}
}
printf("%d\n",ans);
}
int main()
{
scanf("%d",&n);N=sqrt(n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++) pos[i]=(i-1)/N+1;
memset(v,-1,sizeof(v));
for (int i=1;i<=n;i++)
{
int x,y,c;
scanf("%d%d%d",&x,&y,&c);
solve(x,y,c);
}
return 0;
}
loj#6285. 數列分塊入門 9
查詢區間最小衆數
害怕太大所以離散化重新強制遍了一個號
然後利用map表每個元素只能出現一次的特質來存一下每個數離散化後的編號
順便用一波醜醜的vector統計一下每個數第一個出現的位置
然後dp一波把每一個完整塊內的衆數先預處理一下
然後還是像上面那樣
不完整的塊暴力查找
完整的塊直接max一下就好啦
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <map>
#include <vector>
using namespace std;
int pos[110000],v[110000],cnt[110000],a[110000];
int id,n,N;
int f[510][510];//塊i到j之間的最小衆數的id
map<int,int> m;//記錄每個數的id
vector<int> b[51000];//記錄每個數出現的第一個位置
void dp(int x) {
memset(cnt,0,sizeof(cnt));
int maxx=0,ss=0; //最大的id及最大的值
for (int i=(x-1)*N+1; i<=n; i++) {
int cc=++cnt[a[i]]; //統計每個數出現的個數
if (cc>ss||cc==ss&&v[a[i]]<v[maxx]) {ss=cc;maxx=a[i];}
f[x][pos[i]]=maxx;
}
}
int along(int l,int r,int x) //ask how long
{
int ans=upper_bound(b[x].begin(),b[x].end(),r)-lower_bound(b[x].begin(),b[x].end(),l);//求l到r在x塊的長度(r的後繼-l的前驅)
return ans;
}
int solve(int x,int y)
{
int ss=0,maxx=0;
//完整的塊
maxx=f[pos[x]+1][pos[y]-1];
ss=along(x,y,maxx);
//不完整的塊
for (int i=x;i<=min(pos[x]*N,y);i++)
{
int cc=along(x,y,a[i]);
if (cc>ss||cc==ss&&v[a[i]]<v[maxx]) {ss=cc;maxx=a[i];}
}
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=y;i++)
{
int cc=along(x,y,a[i]);
if (cc>ss||cc==ss&&v[a[i]]<v[maxx]) {ss=cc;maxx=a[i];}
}
}
return maxx;
}
int main() {
scanf("%d",&n);
N=sqrt(n);
for (int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
pos[i]=(i-1)/N+1;
if (m[a[i]]==0)
{
m[a[i]]=++id;//a[i]是第幾個出現的
v[id]=a[i];
}
a[i]=m[a[i]];//強制重新編號
b[a[i]].push_back(i);//記錄每一個出現的位置
}
for (int i=1; i<=pos[n]; i++) dp(i);
for (int i=1; i<=n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
if (x>y) swap(x,y);
printf("%d\n",v[solve(x,y)]);
}
return 0;
}
總結:
其實分塊和莫隊一樣都是看着很美妙寫的時候更美妙其實非常暴力的的東西
因爲代碼量相對正常的數據結構來說少很多
所以在接下來的省選中可以嘗試來騙一騙分
AK的大佬們請自動跳過上一句話
祝大家AK
完結撒花~
(終於肝完了累死我了)