分块 Study data Link:http://hzwer.com/8053.html // hzwer讲的很好很全
树上莫队Study Link:http://codeforces.com/blog/entry/43230 ( ery nice 并且有题目推荐
莫队算法时间复杂度O( n * sqrt(n) )证明:
由于每一块的大小为sqrt(n),故有sqrt(n)块,按照所属块为first key ,右端点为second key,考虑每一块,因为每块的右端点是Increase,故右端点最大的移动为N ,左端点的最大移动为 k × sqrt(n),故总复杂度为O(n×sqrt(n) + m×sqrt(n)) ≈ O( n * sqrt(n) )
一道很牛逼的题
题意
给一个n的数的序列,现有q次询问, 1代表是修改操作,2代表是查询操作,对于每次查询输出[1,1e6]以内出现偶数次最小的树(没有出现也算是偶数次) (a[i],n,q<=1e6)
分析
对[1,1e6]分块,每次修改之前的数所在的块和修改后所在的块即可
代码:待补
CDOJ 1324
分析
简单的单点更新,区间最大值
时间复杂度( n×sqrt(n) )
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#define ll long long
using namespace std;
const int maxn = 1e5+7;
//blcok:每块的大小
//num:块的数量
//l[]:每块的左边界,r[]:每块的右边界
//belong[]:每个位置的数属于那一块
int n,q;
int belong[maxn],block,l[maxn],r[maxn],num;
ll a[maxn],Max[maxn];
void build(){
block=sqrt(n);
num=n/block; if(n%num) num++;
for(int i=1;i<=num;i++)
l[i]=(i-1)*block, r[i]=i*block; // 处理出每块的左右边界
r[num]=n;
for(int i=1;i<=n;i++)
belong[i]=(i-1)/block+1; // 每个位置的数属于那一块
for(int i=1;i<=num;i++)
for(int j=l[i];j<=r[i];j++)
Max[i]=max(Max[i],a[j]);
}
void update(int x,int y){
a[x]+=y;
Max[belong[x]]=max(Max[belong[x]],a[x]);
}
ll ask(int x,int y)
{
ll ans=0;
if(belong[x]==belong[y]){
for(int i=x;i<=y;i++)
ans=max(ans,a[i]);
}
else {
for(int i=x;i<=r[belong[x]];i++)
ans=max(ans,a[i]);
for(int i=belong[x]+1;i<=belong[y]-1;i++)
ans=max(ans,Max[i]);
for(int i=l[belong[y]];i<=y;i++)
ans=max(ans,a[i]);
}
return ans;
}
int main()
{
scanf("%d%d", &n, &q);
build();
for(int i=1;i<=q;i++)
{
int op,l,r;
scanf("%d%d%d", &op, &l, &r);
if(op==1) update(l,r);
else
printf("%lld\n", ask(l,r));
}
return 0;
}
莫队入门
codeforces 617E. XOR and Favorite Number
题意
给你一个n个数的序列,有m次询问,每次询问问你 L,R, K,区间[l,r] ,有多少对(i,j)( l ≤ i ≤ j ≤ r )使得 the xor of the numbers ai, ai + 1, ..., aj is equal to k.
(1 ≤ n, m ≤ 1e5, 0 ≤ k ≤ 1e6,0 ≤ ai ≤ 1e6)
分析
由于xor具有 A^A=0性质,处理出xor前缀,区间[L,R]的xor值即为pre[R]^pre[L-1],直接上莫队,一边更新区间[L,R]每个异或值出现的次数,统计时每次只需要询问修改的区间更新(加/减)即可
trick:需要注意更新的先后顺序以及处理[L,R]区间,由于xor的特殊性,需要处理L-1 !!!
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 3e6 +7;
int belong[maxn],a[maxn],num,block, ans[maxn];
ll f[maxn];
int n,m,k;
struct node
{
int l,r,id;
friend bool operator < (node a,node b)
{
if(belong[a.l] == belong[b.l])
return a.r<b.r;
return belong[a.l] < belong[b.l];
}
}q[maxn];
ll Ans=0;
void del(int p) // 因为删除的这个位置不在考虑的区间内,所以先更新ans,在查询
{
ans[a[p]]--;
Ans-=ans[a[p]^k];
}
void add(int p) // 因为加的这个数如果恰好为L-1,这个应不作为考虑,不是L-1的话,这个位置会在下次统计上,所以先查询后更新
{
Ans+=ans[a[p]^k];
ans[a[p]]++;
}
void solve()
{
int L=1,R=0;
for(int i=1;i<=m;i++)
{
while(L<q[i].l) // 由于要得到A[l-1]^A[r] , 故应该先更新,再L++;
{
del(L-1);
L++;
}
while(L>q[i].l){ //当L>q[i].l ,为了便于思考取L=q[i].l+1,其实已经处理了q[i].l这个位置,因为上次统计的时候利用了这个位置
L--;
add(L-1);
}
while(R<q[i].r){ // 右端点没有什么特殊的
R++;
add(R);
}
while(R>q[i].r){
del(R);
R--;
}
f[q[i].id]=Ans;
}
}
int main()
{
ans[0]=1; // 注意什么都不取得时候也有一种情况,当A[t]^K=0时,也有一种情况,L=0时
scanf("%d%d%d", &n, &m, &k);
block=int(sqrt(n));
for(int i=1;i<=n;i++){
scanf("%d", &a[i]);
a[i]^=a[i-1];
belong[i]=(i-1)/block+1;
}
for(int i=1;i<=m;i++){
scanf("%d%d", &q[i].l, &q[i].r);
q[i].id=i;
}
sort(q+1,q+m+1);
solve();
for(int i=1;i<=m;i++)
printf("%lld\n", f[i]);
return 0;
}
分析
莫队板子题
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e6+7;
int belong[maxn],a[maxn],num,block, ans[maxn];
ll fz[maxn],fm[maxn];
int n,m,k;
struct node{
int l,r,id;
friend bool operator < (node a,node b)
{
if(belong[a.l] == belong[b.l])
return a.r<b.r;
return belong[a.l] < belong[b.l];
}
}q[maxn];
ll gcd(ll a, ll b)
{
return b==0? a : gcd(b, a%b);
}
ll Ans=0;
void del(int p)
{
Ans-=ans[a[p]]-1;
ans[a[p]]--;
}
void add(int p)
{
ans[a[p]]++;
Ans+=ans[a[p]]-1;
}
void solve()
{
int L=1,R=0;
for(int i=1;i<=m;i++)
{
while(L<q[i].l)
{
del(L);
L++;
}
while(L>q[i].l)
{
L--;
add(L);
}
while(R<q[i].r)
{
R++;
add(R);
}
while(R>q[i].r)
{
del(R);
R--;
}
ll k=((1LL)*(q[i].r-q[i].l+1))*(q[i].r-q[i].l)/2;
if(Ans!=0)
{
ll g=gcd(Ans,k);
fz[q[i].id]=Ans/g;
fm[q[i].id]=k/g;
}
else
{
fz[q[i].id]=Ans;
fm[q[i].id]=1;
}
}
}
int main()
{
scanf("%d%d", &n, &m, &k);
block=int(sqrt(n));
for(int i=1;i<=n;i++){
scanf("%d", &a[i]);
belong[i]=(i-1)/block+1;
}
for(int i=1;i<=m;i++){
scanf("%d%d", &q[i].l, &q[i].r);
q[i].id=i;
}
sort(q+1,q+m+1);
solve();
for(int i=1;i<=m;i++)
printf("%lld/%lld\n", fz[i],fm[i]);
return 0;
}
codeforces 620 F. Xors on Segments
题意
给一个n个数的序列,给出m个询问 l, r, 问从区间[L,R]中选两个数 a,b(a<=b,可以同一个数),f=a^(a+1)....^(b-1)^b 的最大值(1 ≤ n ≤ 5e4, 1 ≤ m ≤ 5e3 ,1 ≤ ai ≤ 1e6,1<=L<=R<=n)
分析
数据水了放过了n^2,但数组开太大也会T,正解:莫队+Tire
solution:暴力的复杂度是(m×n^2)肯定gg,那么考虑优化,对所有的询问离线,dp[i]: 以a[i]作为开头的元素的最大值,询问所有 j > i,当以 j 为右端点的询问的左端点<= i时更新
时间复杂度:O(n^2 + n×m)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 5e4+7;
int a[maxn],pre[1000007],n,m;
vector<pair<int,int> >v[maxn];
int ans[maxn];
int main()
{
for(int i=1;i<1000007;i++)
pre[i]=pre[i-1]^i;
scanf("%d%d",&n, &m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
int l,r;
for(int i=1;i<=m;i++)
{
scanf("%d%d", &l, &r);
v[r].push_back({l,i});
}
int dp;
for(int i=1;i<=n;i++)
{
dp=0;
for(int j=i;j<=n;j++)
{
dp=max(dp, pre[a[j]] ^ pre[a[i]] ^ min(a[i],a[j]));
for(auto it : v[j])
{
if(it.first<=i) ans[it.second]=max(ans[it.second],dp);
}
}
}
for(int i=1;i<=m;i++)
{
printf("%d\n",ans[i]);
}
return 0;
}
BZOJ 1086 糖果公园 DFS+贪心
分析
dfs的时候统计子树叶子数量,大于等于b时归为一个省,省会就为当前父节点,怎么把子树节点保存下来呢?用一个栈即可。最后剩下小于b直接给最后一个省即可
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 1000+2;
int belong[maxn],q[maxn],top,sz[maxn],cap[maxn],sum;
int head[maxn],nxt[maxn*2],to[maxn*2],tot,k;
int n,b;
void addedge(int u,int v)
{
to[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
void dfs(int x,int fa)
{
q[++top]=x;
for(int i=head[x];i;i=nxt[i])
{
int v=to[i];
if(v!=fa)
{
dfs(v,x);
if(sz[x]+sz[v]>=b)
{
sz[x]=0;
cap[++sum]=x;
//cout<<v<<' '<<x<<' '<<sum<<endl;
while(q[top]!=x)
{
belong[q[top]]=sum;
--top;
}
}
else
sz[x]+=sz[v];
}
}
sz[x]++;
}
int main()
{
scanf("%d%d", &n, &b);
if(n<b){puts("0");return 0;}
int u,v;
for(int i=1;i<=n-1;i++){
scanf("%d%d", &u, &v);
addedge(u,v);
addedge(v,u);
}
dfs(1,0);
if(n==1)
cout<<1<<endl<<1<<endl<<1<<endl;
else
{
printf("%d\n",sum);
for(int i=1;i<=n;i++)
{
if(!belong[i])
belong[i]=sum;
}
for(int i=1;i<=n;i++)
printf("%d%c", belong[i], i==n ? '\n':' ');
for(int i=1;i<=sum;i++)
printf("%d%c", cap[i], i==n?'\n':' ');
}
return 0;
}
题意
在x轴上,给n个区间 [ Li, Ri ],m个询问,每个询问包含k个点,对于每个询问输出这些点在几个区间里 (n,m<=3e5, L,R<= 1e6,所有询问 k 的和不超过 3e5 )
分析
正着做,无论是对于每个点考虑在那些区间 和 那些区间包括那些点都没有想到合适复杂度,换个角度,考虑点的补集所组成的线段集合完全包含那些线段,那么这些线段对答案一定没有贡献,
故那么我们怎么check每个询问点的补集所组成的线段和所给线段的关系?考虑对所有询问离线,按照线段左端点为first key(从大到小), 右端点为second key(从小到大),每个线段的询问时间为third
key(从小到大,因为当已经线段和询问线段重合的情况),这样可以保证每次询问的左端点是递减的,考虑用树状数组加速这个过程,每次更新右端点即可,依次更新即可
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e6+7;
int c[maxn];
int ans[maxn];
struct node{
int l,r,id;
friend bool operator < (node a,node b)
{
if(a.l != b.l)
return a.l>b.l;
else if(a.r!=b.r)
return a.r < b.r;
else
return a.id<b.id;
}
}t[maxn];
int lowbit(int x){
return x&(-x);
}
int sum(int x){
int sum=0;
while(x>0){
sum+=c[x];
x-=lowbit(x);
}
return sum;
}
int modfiy(int x,int val){
while(x<=1000000){
c[x]+=val;
x+=lowbit(x);
}
}
int n,q;
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
{
scanf("%d%d", &t[i].l,&t[i].r);
t[i].id=0;
}
int k;
int tot=n;
for(int i=1;i<=q;i++)
{
scanf("%d", &k);
int st=0;
for(int j=1;j<=k;j++)
{
int x;
scanf("%d", &x);
if(!st)
{
if(x>1)
{
t[++tot].l=1;
t[tot].r=x-1;
t[tot].id=i;
}
st=x;
}
else
{
if(x-1<st+1)
{
}
else
{
t[++tot].l=st+1;
t[tot].r=x-1;
t[tot].id=i;
}
st=x;
}
}
if(st+1<=1000000)
{
t[++tot].l=st+1;
t[tot].r=1000000;
t[tot].id=i;
}
}
sort(t+1, t+tot+1);
for(int i=1;i<=tot;i++)
{
if(t[i].id==0)
{
modfiy(t[i].r,1);
}
else
{
ans[t[i].id]+=sum(t[i].r);
}
}
for(int i=1;i<=q;i++)
printf("%d\n", n-ans[i]);
return 0;
}
Codeforces 375D 树上莫队
题意
给一颗n个节点带权树(权值=颜色),现有m个询问,每个询问问u,k,问以u为根的子树,出现的所有颜色中次数>=k次的数量(根为1,n,m<=1e5 )
分析
Study Link:dsu on tree
解法:莫队/dsu on tree+bit
莫队:求出dfs序,可知每个点的子树的范围,转化为区间上的序列的莫队,每次询问一个根节点相当于询问它的子树区间,对区间分块排序后直接上莫队即可 ( 类比区间
trick:处理>=k的数量很巧妙啊,因为每个颜色数量是逐渐变大的,故直接用一个数组递推过去即可,O(1)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5+7;
int n,m,col[maxn],rcol[maxn],sum[maxn],cnt[maxn];
int head[maxn*2],nxt[maxn*2],to[maxn*2],tot;
int L[maxn],R[maxn], Ans[maxn], block, belong[maxn];
void addedge(int u,int v)
{
to[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
struct node{
int l,r,id,k;
friend bool operator <(node a,node b)
{
if(a.l/block!=b.l/block)
return a.l/block < b.l/block;
return a.r<b.r;
}
}q[maxn];
int tms;
void dfs(int u,int fa){
L[u]=++tms;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v!=fa)
dfs(v,u);
}
R[u]=tms;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++)
scanf("%d", &col[i]);
block=int(sqrt(n));
int u,v;
for(int i=1;i<=n-1;i++){
scanf("%d%d", &u, &v);
addedge(u,v);
addedge(v,u);
}
dfs(1,0);
int x,kk;
for(int i=1;i<=m;i++){
scanf("%d%d", &x, &kk);
q[i].l=L[x];
q[i].r=R[x];
q[i].k=kk;
q[i].id=i;
}
sort(q+1,q+m+1);
for(int i=1;i<=n;i++)
rcol[i]=col[i];
for(int i=1;i<=n;i++)
col[L[i]]=rcol[i];
int L=1,R=0;
for(int i=1;i<=m;i++)
{
while(L<q[i].l)
{
sum[cnt[col[L]]]--;
cnt[col[L]]--;
L++;
}
while(L>q[i].l)
{
L--;
cnt[col[L]]++;
sum[cnt[col[L]]]++;
}
while(R<q[i].r)
{
R++;
cnt[col[R]]++;
sum[cnt[col[R]]]++;
}
while(R>q[i].r)
{
sum[cnt[col[R]]]--;
cnt[col[R]]--;
R--;
}
Ans[q[i].id]=sum[q[i].k];
}
for(int i=1;i<=m;i++)
printf("%d\n", Ans[i]);
return 0;
}
分析
留坑,树上带修改莫队 ,vfk题解Link
树上莫队 SPOJ COT2 - Count on a tree II / BZOJ 3757
题意
SPOJ:给一颗点权数,多次询问路径(a,b)上有多少个权值不同的点(n<=4e5,m<=1e6)
BZOJ:
分析
直观的做法:每次从lca(a,b)分别走到a,b即可,但首先从lca(a,b)出发不能保证走的一定是a,b的路径,最坏的情况是走整棵树,gg
莫队:考虑深搜将树分块,将树上的序列转化成区间上的序列,按照区间上的莫队方法来做
转移:考虑从(u,v)->(u',v')
设S(u,v):u到v路径上的点集,则S(u,v)=S(root,u) xor S(root,v) xor lca(u,v) ( xor运算=对称差:简单的说就是去掉出现两次的 )
由于lca(u,v)很麻烦,那么我们设T(u,v)=S(root,u) xor S(root,v),则T(u',v)=S(root,u') xor S(root,v) (现考虑一个点移动)
T(u',v) xor T(u,v) = S(root,u') xor S(root,v)xor S(root,u) xor S(root,v)
T(u',v) xor T(u,v) = S(root,u') xor S(root,u) = T(u',u)
两边同时 xor T(u,v)
T(u',v) = T(u,v) xor T(u',u)
=> S(a',b) = S(a,b) xor S(a,a') xor lca(a',b)
=> S(a',b')=S(a,b) xor S(a,a') xor S(b,b') xor lca(a',b')
所以:从(a,b)->(a',b') 只需要 a-> a' 和 b-> b'路径上的点取反 ( 除lca(u',v) )的情况取反即可
转移的方法:通过预处理的深度,不断调节深度,直至到达两点lca,但没有处理lca
trick:注意lca(u',v')不是取反,直接取相反的情况即可 ( 即没有对lca(u',v')的数量修改),因为在转移的时候并没有将lca(u',v')考虑进去,
那么有一个问题,若lca(u,v)==lca(u',v'),这样处理没问题,但lca(u,v) != lca(u',v'),其实通过画图观察不难发现,u->u'和v->v' 转移过程都没有改变lca(u,v),故不影响
所以我们只要大胆的把u->u'和v->v' 转移过去就好,最后将lca(u',v')的贡献加进去即可
总结:1. 深搜 分块+预处理深度 2. 求lca主 3. sort 4.莫队转移得到answer
!!!!Trick:树上莫队按块排序 L, R的所在的块都要排序,因为在序列上(eg:数组)从小到大直接就是从近到远,但树上的序号却没有任何关系,因此必须要把L,R的块都排序
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<unordered_map>
using namespace std;
const int maxn = 4e5+7;
int head[maxn],nxt[maxn*2],to[maxn*2],tot;
int col[maxn],n,m,u,v,ans[maxn],Ans;
int belong[maxn],block;
int fa[maxn][32],d[maxn],f[maxn];
bool vis[maxn],vv[maxn];
int g[maxn];
unordered_map<int,int>rg;
struct node{
int l,r,id;
friend bool operator < (node a,node b){
if(belong[a.l] != belong[b.l]) return belong[a.l] < belong[b.l];
else return belong[a.r] < belong[b.r];
}
}q[maxn];
void addedge(int u,int v)
{
to[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
int s[maxn],cnt,sz[maxn],num;
void dfs(int x)
{
vis[x]=1;
s[++cnt]=x;
for(int i=head[x];i;i=nxt[i])
{
int v=to[i];
if(!vis[v])
{
d[v]=d[x]+1;
fa[v][0]=x;
dfs(v);
if(sz[x] + sz[v] >= block)
{
++num;
sz[x]=0;
while(s[cnt]!=x)
{
belong[s[cnt]]=num;
vv[s[cnt]]=1;
--cnt;
}
}
else
sz[x]+=sz[v];
}
}
sz[x]++;
}
void init_rmq()
{
// fa[1][0]=1;
for(int j=1;j<=30;j++)
for(int i=1;i<=n;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
}
int LCA(int u,int v)
{
if(d[u]<d[v])
swap(u,v);
int dc=d[u]-d[v];
for(int i=0;i<30;i++){ //�����度
if((1<<i)&dc)
u=fa[u][i];
}
if(u==v) return u; // �个��好���个��lca
for(int i=30;i>=0;i--){ //æ�¾å�°å�¯ä»¥è·³ç��æ��大ç��æ¥æ�°ï¼�使å¾�(u,v)ç��lcaä¸�å��
if(fa[u][i] != fa[v][i])
{
u=fa[u][i];
v=fa[v][i];
}
}
return fa[u][0]; //u�v�����lca���
}
void rev(int x)
{
if(!vis[x]){
f[col[x]]++;
if(f[col[x]]==1)
Ans++;
vis[x]=1;
}
else{
f[col[x]]--;
if(f[col[x]]==0)
Ans--;
vis[x]=0;
}
}
void solve(int u,int v)
{
while(u!=v)
{
if(d[u]<d[v]){
rev(v),v=fa[v][0];
}
else
{
rev(u),u=fa[u][0];
}
}
}
int main()
{
scanf("%d%d", &n, &m);
block=int(sqrt(n));
for(int i=1;i<=n;i++)
{
scanf("%d", &col[i]);
g[i]=col[i];
}
sort(g+1,g+n+1);
int point=unique(g+1,g+n+1)-g-1;
for(int i=1;i<=point;i++)
rg[g[i]]=i;
for(int i=1;i<=n;i++)
col[i]=rg[col[i]];
for(int i=1;i<=n-1;i++)
{
scanf("%d%d", &u, &v);
addedge(u,v),addedge(v,u);
}
// d[1]=1;
dfs(1);
++num;
for(int i=1;i<=n;i++){
if(!vv[i])
belong[i]=num;
}
init_rmq();
memset(vis,0,sizeof(vis));
for(int i=1;i<=m;i++){
scanf("%d%d", &u, &v);
if(belong[u] > belong[v])
swap(u,v);
q[i].l=u;
q[i].r=v;
q[i].id=i;
}
sort(q+1,q+m+1);
int lca=LCA(q[1].l,q[1].r);
solve(q[1].l,q[1].r);
ans[q[1].id]=Ans+!(f[col[lca]]);
for(int i=2;i<=m;i++)
{
lca=LCA(q[i].l,q[i].r);
solve(q[i-1].l,q[i].l);
solve(q[i-1].r,q[i].r);
ans[q[i].id]=Ans+!(f[col[lca]]);
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}
来源:oschina
链接:https://my.oschina.net/u/4389106/blog/3941814