( ext{Chef and Churu})
解法
拼接两个算法分块。
这道题显然有两种信息维护方式:
- 维护 (A) 的前缀和,对于单个函数 (f(i)),直接查询对应的 (A)。
- 对于 (A_i) 的修改,修改区间包含 (i) 的函数 (f(i))。
第二种维护可以将函数分块,维护每个块内所有 (A_i) 的贡献次数,差分实现可以做到 (mathcal O(ncdot frac{n}{B}))(具体实现请见 getNum()
函数)预处理。但无法处理散块的情况,而散块满足大小在 (B) 范围,所以可以暴力更新函数。
如何维护 (A) 的前缀和?可以用数据结构进行维护,但询问散块复杂度就上升到 (mathcal O(qBcdot log n))。其实只需要维护每块的前缀和和块与块之间的前缀和,这样单次修改是是 (mathcal O(B+frac{n}{B})) 的,询问降到了 (mathcal O(qB))。总体复杂度是 (mathcal O(qcdot(B+frac{n}{B}))),所以 (B) 取 (sqrt n) 即可。
需要注意的是,答案范围 (10^5 imes 10^5 imes 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
",Ans);
}
}
return 0;
}
( ext{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]) 之间即可。
( ext{Update on 2021.8.17}):
对于序列分块。另外 歴史の研究 也可以用类似的方法分块,存一下块的左右端点可以去掉 (log)。
这样的问题可以考虑此种分块:
- 可以预处理/快速得出一段连续块之间的答案。
- 散块的答案可以在 (mathcal O(B)/ mathcal Oleft(frac{n}{B} ight )) 之内拼接。
好像没啥用,我还是不会。
代码
#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,'
');
}
return 0;
}
( ext{Children Trips})
题目大意
给定 (n) 个点的树,每条边长度是 (1) 或 (2)。有 (m) 个询问,每次给出 (x,y,z),你需要算出 (x ightarrow y) 的路径最少能被划分成多少长度不超过 (z) 的段。
(n,mle 10^5)。
解法
考虑计算 (x,y) 的 ( m lca),分别跳上去计算答案,最后几步可以拼起来。
分块可以利用 (z) 的性质:
- (z>sqrt n)。如果每次跳 (z) 步,那么至多跳 (sqrt n) 次。由此可以直接模拟向上跳的过程,每次花费 (mathcal O(log n)) 跳至多 (z) 步,所以是 (mathcal O(msqrt ncdot log n)) 的。
- (zle sqrt n)。(z) 的种数不超过 (sqrt n)。考虑加速向上跳的过程:预处理一个倍增的数组 (up_{i,j}),初始化 (up_{i,0}) 为 (i) 向上跳 (2^0) 个 (z) 到的点。复杂度 (mathcal O(nsqrt ncdot log n+mlog n))。
有个需要注意的点就是不能跳到 ( m 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],'
');
return 0;
}