大厨的分块题
作者:互联网
\(\text{Chef and Churu}\)
解法
这道题显然有两种信息维护方式:
- 维护 \(A\) 的前缀和,对于单个函数 \(f(i)\),直接查询对应的 \(A\)。
- 对于 \(A_i\) 的修改,修改区间包含 \(i\) 的函数 \(f(i)\)。
第二种维护可以将函数分块,维护每个块内所有 \(A_i\) 的贡献次数,差分实现可以做到 \(\mathcal O(n\cdot \frac{n}{B})\)(具体实现请见 getNum()
函数)预处理。但无法处理散块的情况,而散块满足大小在 \(B\) 范围,所以可以暴力更新函数。
如何维护 \(A\) 的前缀和?可以用数据结构进行维护,但询问散块复杂度就上升到 \(\mathcal O(qB\cdot \log n)\)。其实只需要维护每块的前缀和和块与块之间的前缀和,这样单次修改是是 \(\mathcal O(B+\frac{n}{B})\) 的,询问降到了 \(\mathcal O(qB)\)。总体复杂度是 \(\mathcal O(q\cdot(B+\frac{n}{B}))\),所以 \(B\) 取 \(\sqrt n\) 即可。
需要注意的是,答案范围 \(10^5\times 10^5\times 10^9=10^{19}\),要开 unsigned long long
。
代码
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-'),write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <cmath>
#include <iostream>
using namespace std;
typedef unsigned long long ll;
const int maxn=1e5+5;
int n,a[maxn],bl[maxn];
int rk[maxn],cnt,B,L[maxn];
int R[maxn];
struct Block {
int l,r,num[maxn];
ll sum[400],S,ans;
} s[400];
void getSum(int x) {
ll tmp=0;
for(int i=s[x].l;i<=s[x].r;++i) {
tmp+=a[i];
s[x].sum[rk[i]]=tmp;
}
s[x].sum[B]=tmp;
for(int i=x;i<=cnt;++i)
s[i].S=s[i-1].S+s[i].sum[B];
}
ll Query(int x,int y) {
if(bl[x]^bl[y]) {
if(bl[x]+1==bl[y])
return s[bl[y]].sum[rk[y]]-s[bl[x]].sum[rk[x]-1]+s[bl[x]].sum[B];
return s[bl[y]].sum[rk[y]]+s[bl[y]-1].S-s[bl[x]-1].S-s[bl[x]].sum[rk[x]-1];
}
return s[bl[x]].sum[rk[y]]-s[bl[x]].sum[rk[x]-1];
}
void getNum(int x) {
for(int i=s[x].l;i<=s[x].r;++i) {
++s[x].num[L[i]],--s[x].num[R[i]+1];
s[x].ans+=Query(L[i],R[i]);
}
for(int i=1;i<=n;++i)
s[x].num[i]+=s[x].num[i-1];
}
int main() {
n=read(9); B=sqrt(n);
for(int i=1;i<=n;++i)
a[i]=read(9);
for(int i=1;i<=n;++i) {
L[i]=read(9),R[i]=read(9);
bl[i]=(i-1)/B+1;
rk[i]=(i-1)%B+1;
}
cnt=bl[n];
for(int i=1;i<=cnt;++i) {
s[i].l=s[i-1].r+1;
s[i].r=s[i].l+B-1;
}
s[cnt].r=min(s[cnt].r,n);
for(int i=1;i<=cnt;++i)
getSum(i);
for(int i=1;i<=cnt;++i)
getNum(i);
int op,x; ll y,dec,Ans;
for(int m=read(9);m;--m) {
op=read(9),x=read(9),y=read(9);
if(op==1) {
dec=y-a[x],a[x]=y;
getSum(bl[x]);
for(int i=1;i<=cnt;++i)
s[i].ans+=dec*s[i].num[x];
}
else {
Ans=0;
if(bl[x]^bl[y]) {
for(int i=x;i<=s[bl[x]].r;++i)
Ans+=Query(L[i],R[i]);
for(int i=s[bl[y]].l;i<=y;++i)
Ans+=Query(L[i],R[i]);
for(int i=bl[x]+1;i<bl[y];++i)
Ans+=s[i].ans;
}
else {
for(int i=x;i<=y;++i)
Ans+=Query(L[i],R[i]);
}
printf("%llu\n",Ans);
}
}
return 0;
}
\(\text{Chef and Problems}\)
解法
好妙啊,预处理 \(pre_{i,j},suf_{i,j}\) 表示第 \(i\) 种数在 \(j\) 块之后/之前(包括第 \(j\) 块)的最早/最晚出现位置。根据这个可以 \(\mathtt{dp}\) 出 \(f_{i,j}\) 表示 \(i,j\) 块之间的答案。
询问时讨论 \(l,r\) 分别是否在 \([bl_l+1,bl_r-1]\) 之间即可。
代码
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-'),write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <cmath>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn=1e5+5,inf=1e9;
int n,m,q,a[maxn],bl[maxn],B;
int pre[maxn][400],suf[maxn][400];
int f[400][400],Left[maxn];
int main() {
memset(pre,0x3f,sizeof pre);
n=read(9),m=read(9),q=read(9);
B=sqrt(n);
for(int i=1;i<=n;++i)
a[i]=read(9),
bl[i]=(i-1)/B+1,
suf[a[i]][bl[i]]=i;
for(int i=n;i>=1;--i)
pre[a[i]][bl[i]]=i;
for(int i=1;i<=m;++i) {
for(int j=bl[n];j>=1;--j)
pre[a[i]][j]=min(pre[a[i]][j],pre[a[i]][j+1]);
for(int j=1;j<=bl[n];++j)
suf[a[i]][j]=max(suf[a[i]][j],suf[a[i]][j-1]);
}
for(int i=bl[n];i>=1;--i)
for(int j=i;j<=bl[n];++j) {
f[i][j]=max(f[i+1][j],f[i][j-1]);
for(int k=(i-1)*B+1;k<=min(i*B,n);++k)
f[i][j]=max(f[i][j],suf[a[k]][j]-k);
}
int l,r,ans;
while(q--) {
l=read(9),r=read(9);
ans=f[bl[l]+1][bl[r]-1];
for(int i=l;i<=min(bl[l]*B,n);++i)
ans=max(ans,suf[a[i]][bl[r]-1]-i);
for(int i=min(bl[l]*B,n);i>=l;--i)
Left[a[i]]=i;
for(int i=(bl[r]-1)*B;i<=r;++i) {
ans=max(ans,i-pre[a[i]][bl[l]+1]);
if(Left[a[i]])
ans=max(ans,i-Left[a[i]]);
}
for(int i=min(bl[l]*B,n);i>=l;--i)
Left[a[i]]=0;
print(ans,'\n');
}
return 0;
}
\(\text{Children Trips}\)
题目大意
给定 \(n\) 个点的树,每条边长度是 \(1\) 或 \(2\)。有 \(m\) 个询问,每次给出 \(x,y,z\),你需要算出 \(x\rightarrow y\) 的路径最少能被划分成多少长度不超过 \(z\) 的段。
\(n,m\le 10^5\)。
解法
考虑计算 \(x,y\) 的 \(\rm lca\),分别跳上去计算答案,最后几步可以拼起来。
分块可以利用 \(z\) 的性质:
- \(z>\sqrt n\)。如果每次跳 \(z\) 步,那么至多跳 \(\sqrt n\) 次。由此可以直接模拟向上跳的过程,每次花费 \(\mathcal O(\log n)\) 跳至多 \(z\) 步,所以是 \(\mathcal O(m\sqrt n\cdot \log n)\) 的。
- \(z\le \sqrt n\)。\(z\) 的种数不超过 \(\sqrt n\)。考虑加速向上跳的过程:预处理一个倍增的数组 \(up_{i,j}\),初始化 \(up_{i,0}\) 为 \(i\) 向上跳 \(2^0\) 个 \(z\) 到的点。复杂度 \(\mathcal O(n\sqrt n\cdot \log n+m\log n)\)。
有个需要注意的点就是不能跳到 \(\rm lca\),最多只能跳到它下面。这是为了处理最后几步的情况。
代码
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-'),write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair <int,int> pii;
const int maxn=1e5+5;
int n,m,head[maxn],cnt,M;
int ans[maxn],s_cnt,b_cnt;
int f[maxn][20],dep[maxn];
int up[maxn][20],dis[maxn];
struct node {
int x,y,p,id;
bool operator < (const node &t) const {
return p<t.p;
}
} S[maxn],B[maxn];
struct Edge {
int nxt,to,w;
} e[maxn<<1];
void addEdge(int u,int v,int w) {
e[++cnt].w=w;
e[cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
e[++cnt].w=w;
e[cnt].to=u;
e[cnt].nxt=head[v];
head[v]=cnt;
}
int Lca(int x,int y) {
if(dep[x]<dep[y])
swap(x,y);
for(int i=18;i>=0;--i)
if(dep[f[x][i]]>=dep[y])
x=f[x][i];
if(x==y) return x;
for(int i=18;i>=0;--i)
if(f[x][i]^f[y][i]) {
x=f[x][i];
y=f[y][i];
}
return f[x][0];
}
int jump(int x,int p) {
int rec=x;
for(int i=18;i>=0;--i)
if(dis[rec]-dis[f[x][i]]<=p)
x=f[x][i];
return x;
}
pii brute(int x,int y,int p) {
pii r=make_pair(0,0);
while(x^y) {
int z=jump(x,p);
if(dep[z]>dep[y])
++r.first,
x=z;
else
r.second+=dis[x]-dis[y],x=y;
}
return r;
}
pii Jump(int x,int y,int p) {
pii r=make_pair(0,0);
for(int i=18;i>=0;--i)
if(dep[up[x][i]]>dep[y])
x=up[x][i],
r.first+=(1<<i);
r.second=dis[x]-dis[y];
return r;
}
void init(int p) {
for(int i=1;i<=n;++i)
up[i][0]=jump(i,p);
for(int j=1;j<=18;++j)
for(int i=1;i<=n;++i)
up[i][j]=up[up[i][j-1]][j-1];
}
void calc(const node &t) {
if(t.x==t.y) {
ans[t.id]=0;
return;
}
int lca=Lca(t.x,t.y);
pii r1,r2;
if(t.p>M) {
r1=brute(t.x,lca,t.p);
r2=brute(t.y,lca,t.p);
}
else {
r1=Jump(t.x,lca,t.p);
r2=Jump(t.y,lca,t.p);
}
ans[t.id]=r1.first+r2.first;
if(r1.second+r2.second<=t.p)
++ans[t.id];
else ans[t.id]+=2;
}
void b_solve() {
for(int i=1;i<=b_cnt;++i)
calc(B[i]);
}
void s_solve() {
sort(S+1,S+s_cnt+1);
for(int i=1;i<=s_cnt;++i) {
if(i==1 or (S[i].p^S[i-1].p))
init(S[i].p);
calc(S[i]);
}
}
void dfs(int u,int fa) {
f[u][0]=fa,dep[u]=dep[fa]+1;
for(int i=1;i<=18;++i)
f[u][i]=f[f[u][i-1]][i-1];
for(int i=head[u];i;i=e[i].nxt)
if(e[i].to^fa)
dis[e[i].to]=dis[u]+e[i].w,
dfs(e[i].to,u);
}
int main() {
n=read(9); M=sqrt(n);
int u,v,w;
for(int i=1;i<n;++i) {
u=read(9),v=read(9),w=read(9);
addEdge(u,v,w);
}
m=read(9); dfs(1,0);
for(int i=1;i<=m;++i) {
u=read(9),v=read(9),w=read(9);
if(w>M)
B[++b_cnt]=(node){u,v,w,i};
else
S[++s_cnt]=(node){u,v,w,i};
}
b_solve(),s_solve();
for(int i=1;i<=m;++i)
print(ans[i],'\n');
return 0;
}
标签:10,const,分块,int,--,maxn,include,大厨 来源: https://www.cnblogs.com/AWhiteWall/p/15139575.html