最近花了一点时间完成了前不久题单还剩下的一些部分,现在小小的做一个总结,个人感觉树链剖分题目类型还是很有规律的,都是可以做的,可能我还没有遇到特别难的题吧。我觉得树链剖分最主要的就是怎么与线段树结合将路径信息转变成区间线段信息,以及在线段树中懒标记的使用,而且树链剖分代码量有点大,所以需要很细心。
[USACO11DEC]Grass Planting G
这道题就是把边权换成点权就行了,然后区间修改,维护一个懒标记,单点询问。
代码如下:
#include
#define all(x) (x).begin(),(x).end()
#define le(x) ((int)(x).size())
#define LL long long
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define db printf("Here!\n");
using namespace std;
const double eps=1e-6;
const LL inf=1e8;
const int N=2e5+5;
const int M=4e5+5;
const int mod=1e9+9;
int n,m,k,t,T,len,op,z,x,y;
int id[N],son[N],top[N],head[M],nxt[M],to[M],siz[N],f[N],dep[N];
char s[4];
struct node{
int l,r,v,lan;
}q[N*4];
void add(int x,int y){nxt[++t]=head[x];head[x]=t;to[t]=y;}
void dfs1(int now,int fa){
f[now]=fa;
dep[now]=dep[fa]+1;
siz[now]=1;
int mx=0;
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa)continue;
dfs1(u,now);
siz[now]+=siz[u];
if(siz[u]>siz[mx])mx=u;
}
if(mx)son[now]=mx;
}
void dfs2(int now,int fa){
id[now]=++T;
top[now]=fa;
if(!son[now])return;
dfs2(son[now],fa);
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa||id[u])continue;
dfs2(u,u);
}
}
void build(int now,int L,int R){
q[now].l=L,q[now].r=R;
if(L==R)return;
int mid=L+R>>1;
build(now<<1,L,mid);
build(now<<1|1,mid+1,R);
}
void push(int now){
int lc=(now<<1),rc=(now<<1|1);
q[lc].v+=q[now].lan;
q[lc].lan+=q[now].lan;
q[rc].v+=q[now].lan;
q[rc].lan+=q[now].lan;
q[now].lan=0;
}
void update(int now,int L,int R){
if(q[now].l>=L&&q[now].r<=R){
q[now].v++;
q[now].lan++;
return;
}
if(q[now].lan)push(now);
int mid=q[now].l+q[now].r>>1;
if(mid>=L)update(now<<1,L,R);
if(R>mid)update(now<<1|1,L,R);
}
int query(int now,int pos){
if(q[now].l==pos&&q[now].r==pos)return q[now].v;
if(q[now].lan)push(now);
int mid=q[now].l+q[now].r>>1;
if(mid>=pos)return query(now<<1,pos);
else return query(now<<1|1,pos);
}
void addpath(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
update(1,id[top[x]],id[x]);
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
update(1,id[x]+1,id[y]);
}
void solve(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
dfs1(1,1);
dfs2(1,1);
build(1,1,n);
while(m--){
scanf("%s%d%d",s,&x,&y);
if(s[0]=='Q')printf("%d\n",query(1,max(id[x],id[y])));
else addpath(x,y);
}
}
int main(){
//int o;scanf("%d",&o);
//while(o--){
solve();
//}
return 0;
}
[USACO15DEC]Max Flow P
这道题转换一下就是区间加,最后再输出一下全局最大值即可。
代码如下:
#include
#define all(x) (x).begin(),(x).end()
#define le(x) ((int)(x).size())
#define LL long long
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define db printf("Here!\n");
using namespace std;
const double eps=1e-6;
const LL inf=1e8;
const int N=2e5+5;
const int M=4e5+5;
const int mod=1e9+9;
int n,m,k,t,T,len,op,z,x,y;
int id[N],son[N],top[N],head[M],nxt[M],to[M],siz[N],f[N],dep[N];
char s[4];
struct node{
int l,r,v,lan,mx;
}q[N*4];
void add(int x,int y){nxt[++t]=head[x];head[x]=t;to[t]=y;}
void dfs1(int now,int fa){
f[now]=fa;
dep[now]=dep[fa]+1;
siz[now]=1;
int mx=0;
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa)continue;
dfs1(u,now);
siz[now]+=siz[u];
if(siz[u]>siz[mx])mx=u;
}
if(mx)son[now]=mx;
}
void dfs2(int now,int fa){
id[now]=++T;
top[now]=fa;
if(!son[now])return;
dfs2(son[now],fa);
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa||id[u])continue;
dfs2(u,u);
}
}
void change(int now){
q[now].mx=max(q[now<<1].mx,q[now<<1|1].mx);
}
void build(int now,int L,int R){
q[now].l=L,q[now].r=R;
if(L==R)return;
int mid=L+R>>1;
build(now<<1,L,mid);
build(now<<1|1,mid+1,R);
}
void push(int now){
int lc=(now<<1),rc=(now<<1|1);
q[lc].v+=q[now].lan;
q[lc].lan+=q[now].lan;
q[lc].mx+=q[now].lan;
q[rc].v+=q[now].lan;
q[rc].lan+=q[now].lan;
q[rc].mx+=q[now].lan;
q[now].lan=0;
}
void update(int now,int L,int R){
if(q[now].l>=L&&q[now].r<=R){
q[now].v++;
q[now].lan++;
q[now].mx++;
return;
}
if(q[now].lan)push(now);
int mid=q[now].l+q[now].r>>1;
if(mid>=L)update(now<<1,L,R);
if(R>mid)update(now<<1|1,L,R);
change(now);
}
int query(int now,int pos){
if(q[now].l==pos&&q[now].r==pos)return q[now].v;
if(q[now].lan)push(now);
int mid=q[now].l+q[now].r>>1;
if(mid>=pos)return query(now<<1,pos);
else return query(now<<1|1,pos);
}
void addpath(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
update(1,id[top[x]],id[x]);
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
update(1,id[x],id[y]);
}
void solve(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
dfs1(1,1);
dfs2(1,1);
build(1,1,n);
while(m--){
scanf("%d%d",&x,&y);
addpath(x,y);
}
printf("%d\n",q[1].mx);
}
int main(){
//int o;scanf("%d",&o);
//while(o--){
solve();
//}
return 0;
}
[BJOI2018]求和
这道题求路径上深度的k次方和,由于k最多只有50,所以我们可以像树上求和的思路一样,在树链剖分的时候维护一下路径上的点到根的点权的k次方和,然后询问的时候求LCA时累加答案即可。需要注意的是,求和时,最后还要加上lca的深度的k次方和。当然这题可以用倍增解了。
代码如下:
#include
#define all(x) (x).begin(),(x).end()
#define le(x) ((int)(x).size())
#define LL long long
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define db printf("Here!\n");
using namespace std;
const double eps=1e-6;
const LL inf=1e8;
const int N=3e5+5;
const int M=6e5+5;
const int mod=998244353;
int n,m,k,t,T,len,op,z,x,y;
int id[N],son[N],top[N],head[M],nxt[M],to[M],siz[N],f[N],dep[N];
LL a[N][51];
void add(int x,int y){nxt[++t]=head[x];head[x]=t;to[t]=y;}
void dfs1(int now,int fa){
f[now]=fa;
dep[now]=dep[fa]+1;
siz[now]=1;
int mx=0;
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa)continue;
dfs1(u,now);
siz[now]+=siz[u];
if(siz[u]>siz[mx])mx=u;
}
if(mx)son[now]=mx;
}
void dfs2(int now,int fa){
id[now]=++T;
top[now]=fa;
LL base=dep[now];
for(int i=1;i<=50;i++){
a[now][i]=(a[f[now]][i]+base)%mod;
base=(base*dep[now])%mod;
}
if(!son[now])return;
dfs2(son[now],fa);
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa||id[u])continue;
dfs2(u,u);
}
}
int lca(int x,int y){
for(;top[x]!=top[y];dep[top[x]]>dep[top[y]]?x=f[top[x]]:y=f[top[y]]);
return dep[x]>dep[y]?y:x;
}
LL ksm(LL x,LL y){
if(x==0)return 0;
LL res=1;
while(y){
if(y&1)res=res*x%mod;
y>>=1;
x=x*x%mod;
}
return res;
}
void solve(){
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
for(int i=0;i<=50;i++)a[i][0]=1;
dep[1]=-1;
dfs1(1,1);
dfs2(1,1);
scanf("%d",&m);
while(m--){
scanf("%d%d%d",&x,&y,&k);
z=lca(x,y);
printf("%lld\n",((a[x][k]+a[y][k]-2*a[z][k]+ksm(dep[z],k))%mod+mod)%mod);
}
}
int main(){
//int o;scanf("%d",&o);
//while(o--){
solve();
//}
return 0;
}
[USACO19FEB]Cow Land G
这道题就是单点修改,区间求和。
[HEOI2016/TJOI2016]树
这道题要求距离自己最近的打了标记的点,首先最容易想到的就是二分+树链剖分,如果该点被打了标记,那么只需要把这个点的权值赋值任何一个不为0的数就行,然后询问时,向根跳的时候,判断当前路径上是否存在点权不为0的点,然后在这个路径上二分就行了。然后其实可以不用这么麻烦,我们把点权赋值为对应的时间戳,因为距离自己最近的一定是时间戳最大的,所以更新的时候就把点权赋值为对应的时间戳就行了。询问的时候就询问路径上最大的时间戳就行。但是这种方法竟然比二分慢有点难以理解。
代码如下:
#include
#define all(x) (x).begin(),(x).end()
#define le(x) ((int)(x).size())
#define LL long long
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define db printf("Here!\n");
using namespace std;
const double eps=1e-6;
const LL inf=1e8;
const int N=2e5+5;
const int M=4e5+5;
const int mod=998244353;
int n,m,k,t,T,len,op,z,x,y;
int id[N],son[N],top[N],head[M],nxt[M],to[M],siz[N],f[N],dep[N],a[N];
char s[4];
struct node{
int l,r,mx,lan;
}q[N*4];
void add(int x,int y){nxt[++t]=head[x];head[x]=t;to[t]=y;}
void dfs1(int now,int fa){
f[now]=fa;
dep[now]=dep[fa]+1;
siz[now]=1;
int mx=0;
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa)continue;
dfs1(u,now);
siz[now]+=siz[u];
if(siz[u]>siz[mx])mx=u;
}
if(mx)son[now]=mx;
}
void dfs2(int now,int fa){
id[now]=++T;
top[now]=fa;
a[T]=now;
if(!son[now])return;
dfs2(son[now],fa);
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa||id[u])continue;
dfs2(u,u);
}
}
void change(int now){
q[now].mx=max(q[now<<1].mx,q[now<<1|1].mx);
}
void build(int now,int L,int R){
q[now].l=L,q[now].r=R;
if(L==R){
if(L==1)q[now].mx=1;
return;
}
int mid=L+R>>1;
build(now<<1,L,mid);
build(now<<1|1,mid+1,R);
change(now);
}
void update(int now,int L,int R,int va){
if(q[now].l>=L&&q[now].r<=R){
q[now].mx=va;
return;
}
//if(q[now].lan)push(now);
int mid=q[now].l+q[now].r>>1;
if(mid>=L)update(now<<1,L,R,va);
else update(now<<1|1,L,R,va);
change(now);
}
int query(int now,int L,int R){
if(q[now].l>=L&&q[now].r<=R){
return q[now].mx;
}
//if(q[now].lan)push(now);
int res=0;
int mid=q[now].l+q[now].r>>1;
if(mid>=L)res=max(res,query(now<<1,L,R));
if(R>mid)res=max(res,query(now<<1|1,L,R));
return res;
}
int querypath(int x,int y){
int res=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
res=max(res,query(1,id[top[x]],id[x]));
if(res)return res;
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
res=max(res,query(1,id[x],id[y]));
return res;
}
void solve(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
dfs1(1,1);
dfs2(1,1);
build(1,1,n);
while(m--){
scanf("%s%d",s,&x);
if(s[0]=='Q'){
printf("%d\n",a[querypath(x,1)]);
}else update(1,id[x],id[x],id[x]);
}
}
int main(){
//int o;scanf("%d",&o);
//while(o--){
solve();
//}
return 0;
}
月下“毛景树”
这道题就是典型的将树的问题转换为区间问题,维护一个线段树,支持区间和单点修改,然后区间询问,首先这里要把路径权值转变为点权。
代码如下:
#include
#define all(x) (x).begin(),(x).end()
#define siz(x) (x.size())
#define LL long long
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define db printf("Here!\n");
using namespace std;
const double eps=1e-6;
const LL inf=1e8;
const int N=1e5+5;
const int M=6e5+5;
const int mod=10007;
int n,m,k,t,T,len,x,y,z;
int dep[N],f[N],top[N],id[N],son[N],siz[N];
int a[N],b[N];
char s[10];
struct node{
int l,r;
int lan,mx,add;
}q[N*4];
vector<pair<int,int> >v[N];
void dfs1(int now,int fa,int val){
dep[now]=dep[fa]+1;
f[now]=fa;
siz[now]=1;
a[now]=val;
int mx=0;
for(auto e:v[now]){
if(e.fi==fa)continue;
dfs1(e.fi,now,e.se);
siz[now]+=siz[e.fi];
if(siz[e.fi]>siz[mx])mx=e.fi;
}
if(mx)son[now]=mx;
}
void dfs2(int now,int fa){
id[now]=++T;
b[T]=a[now];
top[now]=fa;
if(!son[now])return;
dfs2(son[now],fa);
for(auto e:v[now]){
if(e.fi==fa||e.fi==son[now]||id[e.fi])continue;
dfs2(e.fi,e.fi);
}
}
void change(int now){
q[now].mx=max(q[now<<1].mx,q[now<<1|1].mx);
}
void build(int now,int l,int r){
q[now].l=l;q[now].r=r;q[now].mx=0;q[now].lan=-1;
if(l==r){
q[now].mx=b[l];
return ;
}
int mid=l+r>>1;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
change(now);
}
void push(int now){
int lc=(now<<1),rc=(now<<1|1);
if(q[now].lan>=0)q[lc].lan=q[now].lan,q[lc].mx=q[now].lan,q[lc].add=0;
if(q[now].add)q[lc].mx+=q[now].add;
q[lc].add+=q[now].add;
if(q[now].lan>=0)q[rc].lan=q[now].lan,q[rc].mx=q[now].lan,q[rc].add=0;
if(q[now].add)q[rc].mx+=q[now].add;
q[rc].add+=q[now].add;
q[now].add=0;
q[now].lan=-1;
}
void update(int now,int L,int R,int val,int kind){
if(q[now].l>=L&&q[now].r<=R){
if(kind){
q[now].mx=val;
q[now].lan=val;
q[now].add=0;
}else {
q[now].mx+=val;
q[now].add+=val;
}
return;
}
if(q[now].lan!=-1||q[now].add)push(now);
int mid=q[now].l+q[now].r>>1;
if(mid>=L)update(now<<1,L,R,val,kind);
if(R>mid)update(now<<1|1,L,R,val,kind);
change(now);
}
int query(int now,int L,int R){
if(L>R)return 0;
if(q[now].l>=L&&q[now].r<=R){
return q[now].mx;
}
if(q[now].lan!=-1||q[now].add)push(now);
int res=-inf;
int mid=q[now].l+q[now].r>>1;
if(mid>=L){
auto e=query(now<<1,L,R);
res=max(res,e);
}
if(R>mid){
auto e=query(now<<1|1,L,R);
res=max(res,e);
}
return res;
}
int qmax(int x,int y){
if(x==y)return 0;
int res=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
auto e=query(1,id[top[x]],id[x]);
res=max(res,e);
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
auto e=query(1,id[x]+1,id[y]);
res=max(res,e);
return res;
}
void changePath(int x,int y,int z,int kind){
if(x==y)return;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
update(1,id[top[x]],id[x],z,kind);
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
update(1,id[x]+1,id[y],z,kind);
}
pair<int,int>op[N];
void solve(){
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
op[i].fi=x;op[i].se=y;
v[x].pb(mp(y,z));v[y].pb(mp(x,z));
}
dfs1(1,1,0);
dfs2(1,1);
build(1,1,n);
while(scanf("%s",s)!=EOF){
if(s[1]=='t')break;
scanf("%d%d",&x,&y);
if(s[1]=='h'){
if(id[op[x].fi]>id[op[x].se])update(1,id[op[x].fi],id[op[x].fi],y,1);
else update(1,id[op[x].se],id[op[x].se],y,1);
}else if(s[1]=='o'){
scanf("%d",&z);
changePath(x,y,z,1);
}else if(s[1]=='d'){
scanf("%d",&z);
changePath(x,y,z,0);
}else printf("%d\n",qmax(x,y));
}
}
int main(){
//int o;scanf("%d",&o);
//while(o--){
solve();
//}
return 0;
}
[ZJOI2008]树的统计
这道题跟上面那题类似,变成单点修改,区间询问最大值和区间和了,思路都是一样的。
[SHOI2012]魔法树
当然这道题跟上面也类似,只不过不用转换边权了。
[NOI2015]软件包管理器
这道题可以把依赖关系反向建边,当药安装某个软件的时候,就是询问这个软件到根一共有多少个节点,并且有多少个节点已经安装了,这里我们可以把安装的节点赋值为1,不安装的赋值为0.当要卸载某个软件时,就是看他的子树中有多少个安装过的节点,所以这道题就是单点和区间修改,区间询问,单点是安装的时候,区间是卸载的时候。
代码如下:
#include
#define all(x) (x).begin(),(x).end()
#define le(x) ((int)(x).size())
#define LL long long
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define db printf("Here!\n");
using namespace std;
const double eps=1e-6;
const LL inf=1e8;
const int N=2e5+5;
const int M=4e5+5;
const int mod=998244353;
int n,m,k,t,T,len,op,z,x,y;
int id[N],son[N],top[N],head[M],nxt[M],to[M],siz[N],f[N],dep[N],a[N];
char s[20];
struct node{
int l,r,v,lan;
int len(){return r-l+1;}
}q[N*4];
void add(int x,int y){nxt[++t]=head[x];head[x]=t;to[t]=y;}
void dfs1(int now,int fa){
f[now]=fa;
dep[now]=dep[fa]+1;
siz[now]=1;
int mx=0;
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa)continue;
dfs1(u,now);
siz[now]+=siz[u];
if(siz[u]>siz[mx])mx=u;
}
if(mx)son[now]=mx;
}
void dfs2(int now,int fa){
id[now]=++T;
top[now]=fa;
if(!son[now])return;
dfs2(son[now],fa);
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa||id[u])continue;
dfs2(u,u);
}
}
void change(int now){
q[now].v=q[now<<1].v+q[now<<1|1].v;
}
void build(int now,int L,int R){
q[now].l=L,q[now].r=R;q[now].lan=-1;
if(L==R){
return;
}
int mid=L+R>>1;
build(now<<1,L,mid);
build(now<<1|1,mid+1,R);
}
void push(int now){
int lc=(now<<1),rc=(now<<1|1);
q[lc].v=q[now].lan*q[lc].len();
q[lc].lan=q[now].lan;
q[rc].lan=q[now].lan;
q[rc].v=q[now].lan*q[rc].len();
q[now].lan=-1;
}
void update(int now,int L,int R,int va){
if(q[now].l>=L&&q[now].r<=R){
q[now].v=va*q[now].len();
q[now].lan=va;
return;
}
if(q[now].lan!=-1)push(now);
int mid=q[now].l+q[now].r>>1;
if(mid>=L)update(now<<1,L,R,va);
if(R>mid)update(now<<1|1,L,R,va);
change(now);
}
int query(int now,int L,int R){
if(q[now].l>=L&&q[now].r<=R){
return q[now].v;
}
if(q[now].lan!=-1)push(now);
int res=0;
int mid=q[now].l+q[now].r>>1;
if(mid>=L)res+=query(now<<1,L,R);
if(R>mid)res+=query(now<<1|1,L,R);
return res;
}
void addpath(int x,int y,int va){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
update(1,id[top[x]],id[x],va);
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
update(1,id[x],id[y],va);
}
pair<int,int> querypath(int x,int y){
pair<int,int> res=mp(0,0);
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
res.fi+=query(1,id[top[x]],id[x]);
res.se+=(id[x]-id[top[x]]+1);
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
res.fi+=query(1,id[x],id[y]);
res.se+=(id[y]-id[x]+1);
return res;
}
void solve(){
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d",&x);
x++;y=i+1;
add(x,y);add(y,x);
}
dfs1(1,1);
dfs2(1,1);
build(1,1,n);
scanf("%d",&m);
while(m--){
scanf("%s%d",s,&x);
x++;
if(s[0]=='i'){
auto e=querypath(x,1);
printf("%d\n",e.se-e.fi);
addpath(x,1,1);
}else {
//printf("x=%d l=%d r=%d\n",x,id[x],id[x]+siz[x]-1);
printf("%d\n",query(1,id[x],id[x]+siz[x]-1));
update(1,id[x],id[x]+siz[x]-1,0);
}
}
}
int main(){
//int o;scanf("%d",&o);
//while(o--){
solve();
//}
return 0;
}
[SDOI2011]染色
对于树链上的不同颜色段的个数,我们在进行合并时与区间的合并一样,但是在询问时,我们还要注意在一条链跳到另一条链上时两条链相交位置的颜色是否相同,如果相同则答案要减1.同时由于区间修改颜色,我们需要维护一个懒标记。
代码如下:
#include
#define all(x) (x).begin(),(x).end()
#define le(x) ((int)(x).size())
#define LL long long
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define db printf("Here!\n");
using namespace std;
const double eps=1e-6;
const LL inf=1e8;
const int N=1e5+5;
const int M=2e5+5;
const int mod=998244353;
int n,m,k,t,T,len,op,z,x,y;
int id[N],son[N],top[N],head[M],nxt[M],to[M],siz[N],f[N],dep[N],a[N],b[N];
char s[20];
struct node{
int l,r,v,lan,lcol,rcol;
int len(){return r-l+1;}
}q[N*4];
void add(int x,int y){nxt[++t]=head[x];head[x]=t;to[t]=y;}
void dfs1(int now,int fa){
f[now]=fa;
dep[now]=dep[fa]+1;
siz[now]=1;
int mx=0;
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa)continue;
dfs1(u,now);
siz[now]+=siz[u];
if(siz[u]>siz[mx])mx=u;
}
if(mx)son[now]=mx;
}
void dfs2(int now,int fa){
id[now]=++T;
top[now]=fa;
b[id[now]]=a[now];
if(!son[now])return;
dfs2(son[now],fa);
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa||id[u])continue;
dfs2(u,u);
}
}
void change(int now){
q[now].v=q[now<<1].v+q[now<<1|1].v;
q[now].lcol=q[now<<1].lcol;
q[now].rcol=q[now<<1|1].rcol;
if(q[now<<1].rcol==q[now<<1|1].lcol)q[now].v--;
}
void build(int now,int L,int R){
q[now].l=L,q[now].r=R;q[now].lan=0;
if(L==R){
q[now].lcol=q[now].rcol=b[L];
q[now].v=1;
return;
}
int mid=L+R>>1;
build(now<<1,L,mid);
build(now<<1|1,mid+1,R);
change(now);
}
void push(int now){
int lc=(now<<1),rc=(now<<1|1);
q[lc].lcol=q[now].lan;
q[lc].rcol=q[now].lan;
q[lc].lan=q[now].lan;
q[rc].lan=q[now].lan;
q[rc].lcol=q[now].lan;
q[rc].rcol=q[now].lan;
q[lc].v=q[rc].v=1;
q[now].lan=0;
}
void update(int now,int L,int R,int va){
if(q[now].l>=L&&q[now].r<=R){
q[now].v=1;
q[now].lcol=q[now].rcol=q[now].lan=va;
return;
}
if(q[now].lan)push(now);
int mid=q[now].l+q[now].r>>1;
if(mid>=L)update(now<<1,L,R,va);
if(R>mid)update(now<<1|1,L,R,va);
change(now);
}
int query(int now,int L,int R){
if(q[now].l>=L&&q[now].r<=R){
return q[now].v;
}
if(q[now].lan)push(now);
int res=0,ok=0;
int mid=q[now].l+q[now].r>>1;
if(mid>=L)res+=query(now<<1,L,R),ok++;
if(R>mid)res+=query(now<<1|1,L,R),ok++;
if(ok==2)res-=(q[now<<1].rcol==q[now<<1|1].lcol);//如果ok等于2,说明询问跨两个子区间,那么如果两个子区间交汇处颜色相同答案就要减1
return res;
}
void addpath(int x,int y,int va){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
update(1,id[top[x]],id[x],va);
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
update(1,id[x],id[y],va);
}
int querycol(int now,int pos){
if(q[now].l==pos&&q[now].r==pos){
return q[now].lcol;
}
if(q[now].lan)push(now);
int mid=q[now].l+q[now].r>>1;
if(mid>=pos)return querycol(now<<1,pos);
else return querycol(now<<1|1,pos);
}
int querypath(int x,int y){
int res=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
res+=query(1,id[top[x]],id[x]);
if(querycol(1,id[top[x]])==querycol(1,id[f[top[x]]]))res--;//如果两条链交点颜色相同,则答案要减1
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
res+=query(1,id[x],id[y]);
return res;
}
void solve(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
dfs1(1,1);
dfs2(1,1);
build(1,1,n);
while(m--){
scanf("%s%d%d",s,&x,&y);
if(s[0]=='Q'){
printf("%d\n",querypath(x,y));
}else {
scanf("%d",&z);
addpath(x,y,z);
}
}
}
int main(){
//int o;scanf("%d",&o);
//while(o--){
solve();
//}
return 0;
}
[HAOI2015]树上操作
这道题就是单点和区间修改,区间询问和。不知道为什么是一道省选题。
部落冲突
这道题转换一下就是把部落与部落之间的战争转换为点权上,这里我把点权赋值为1,询问的时候就是看路径上点权和是否为0。
代码如下:
#include
#define all(x) (x).begin(),(x).end()
#define le(x) ((int)(x).size())
#define LL long long
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define db printf("Here!\n");
using namespace std;
const double eps=1e-6;
const LL inf=1e8;
const int N=3e5+5;
const int M=6e5+5;
const int mod=998244353;
int n,m,k,t,T,len,op,z,x,y;
int id[N],son[N],top[N],head[M],nxt[M],to[M],siz[N],f[N],dep[N];
char s[20];
struct node{
int l,r;
int va;
}q[N*4];
void add(int x,int y){nxt[++t]=head[x];head[x]=t;to[t]=y;}
void dfs1(int now,int fa){
f[now]=fa;
dep[now]=dep[fa]+1;
siz[now]=1;
int mx=0;
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa)continue;
dfs1(u,now);
siz[now]+=siz[u];
if(siz[u]>siz[mx])mx=u;
}
if(mx)son[now]=mx;
}
void dfs2(int now,int fa){
id[now]=++T;
top[now]=fa;
if(!son[now])return;
dfs2(son[now],fa);
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa||id[u])continue;
dfs2(u,u);
}
}
void change(int now){
q[now].va=q[now<<1].va+q[now<<1|1].va;
}
void build(int now,int l,int r){
q[now].l=l;q[now].r=r;
if(l==r){
return ;
}
int mid=l+r>>1;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
}
void update(int now,int L,int R,int val){
if(q[now].l>=L&&q[now].r<=R){
q[now].va=val;
return;
}
// if(q[now].lan)push(now);
int mid=q[now].l+q[now].r>>1;
if(mid>=L)update(now<<1,L,R,val);
if(R>mid)update(now<<1|1,L,R,val);
change(now);
}
int query(int now,int L,int R){
if(q[now].l>=L&&q[now].r<=R){
return q[now].va;
}
int res=0;
int mid=q[now].l+q[now].r>>1;
if(mid>=L)res+=query(now<<1,L,R);
if(R>mid)res+=query(now<<1|1,L,R);
return res;
}
int queryPath(int x,int y){
int res=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
res+=query(1,id[top[x]],id[x]);
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
res+=query(1,id[x]+1,id[y]);
return res;
}
pair<int,int>p[N];
void solve(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
dfs1(1,1);
dfs2(1,1);
build(1,1,n);
while(m--){
scanf("%s%d",&s,&x);
if(s[0]=='U'){
op=max(id[p[x].fi],id[p[x].se]);
update(1,op,op,0);
}else if(s[0]=='C'){
scanf("%d",&y);
op=max(id[x],id[y]);
update(1,op,op,1);
p[++k].fi=x;
p[k].se=y;
}else {
scanf("%d",&y);
puts(!queryPath(x,y)?"Yes":"No");
}
}
}
int main(){
//int o;scanf("%d",&o);
//while(o--){
solve();
//}
return 0;
}
Qtree1
这道题就是边权换成点权,单点修改,区间查询。
Qtree3
这道题跟上面的树那道题正好反过来,所以我们只需要询问时返回区间时间戳最小的那个就行了。
[国家集训队]旅游
这道题就是线段树懒标记的使用吧,区间边权变成相反数时,区间最大值和最小值数值互换,区间和变成相反数,然后维护一下就行了。
代码如下:
#include
#define all(x) (x).begin(),(x).end()
#define le(x) ((int)(x).size())
#define LL long long
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define db printf("Here!\n");
using namespace std;
const double eps=1e-6;
const LL inf=1e8;
const int N=3e5+5;
const int M=6e5+5;
const int mod=998244353;
int n,m,k,t,T,len,op,z,x,y;
int id[N],son[N],top[N],head[M],nxt[M],to[M],siz[N],f[N],dep[N],va[M],a[N],b[N];
char s[20];
struct node{
int l,r,lan;
int v,mx,mi;
}q[N*4];
void add(int x,int y,int z){nxt[++t]=head[x];head[x]=t;to[t]=y;va[t]=z;}
void dfs1(int now,int fa,int val){
f[now]=fa;
dep[now]=dep[fa]+1;
siz[now]=1;
a[now]=val;
int mx=0;
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa)continue;
dfs1(u,now,va[i]);
siz[now]+=siz[u];
if(siz[u]>siz[mx])mx=u;
}
if(mx)son[now]=mx;
}
void dfs2(int now,int fa){
id[now]=++T;
top[now]=fa;
b[id[now]]=a[now];
if(!son[now])return;
dfs2(son[now],fa);
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa||id[u])continue;
dfs2(u,u);
}
}
void change(int now){
q[now].mx=max(q[now<<1].mx,q[now<<1|1].mx);
q[now].mi=min(q[now<<1].mi,q[now<<1|1].mi);
q[now].v=q[now<<1].v+q[now<<1|1].v;
}
void build(int now,int l,int r){
q[now].l=l;q[now].r=r;q[now].lan=1;
if(l==r){
q[now].mi=q[now].mx=q[now].v=b[l];
return ;
}
int mid=l+r>>1;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
change(now);
}
void push(int now){
int lc=(now<<1),rc=(now<<1|1);
q[lc].lan*=-1;
int temp=q[lc].mi;
q[lc].mi=-q[lc].mx;
q[lc].mx=-temp;
q[lc].v*=-1;
q[rc].lan*=-1;
temp=q[rc].mi;
q[rc].mi=-q[rc].mx;
q[rc].mx=-temp;
q[rc].v*=-1;
q[now].lan=1;
}
void update(int now,int L,int R){
if(q[now].l>=L&&q[now].r<=R){
q[now].v*=-1;
q[now].lan*=-1;
int temp=q[now].mi;
q[now].mi=-q[now].mx;
q[now].mx=-temp;
return;
}
if(q[now].lan==-1)push(now);
int mid=q[now].l+q[now].r>>1;
if(mid>=L)update(now<<1,L,R);
if(R>mid)update(now<<1|1,L,R);
change(now);
}
void updateVal(int now,int pos,int val){
if(q[now].l==pos&&q[now].r==pos){
q[now].v=val;
q[now].mi=val;
q[now].mx=val;
return;
}
if(q[now].lan==-1)push(now);
int mid=q[now].l+q[now].r>>1;
if(mid>=pos)updateVal(now<<1,pos,val);
else updateVal(now<<1|1,pos,val);
change(now);
}
pair<int,pair<int,int> > query(int now,int L,int R){
if(q[now].l>=L&&q[now].r<=R){
return mp(q[now].v,mp(q[now].mx,q[now].mi));
}
if(q[now].lan==-1)push(now);
pair<int,pair<int,int> > res=mp(0,mp(-inf,inf));
int mid=q[now].l+q[now].r>>1;
if(mid>=L){
auto e=query(now<<1,L,R);
res.fi+=e.fi;
res.se.fi=max(res.se.fi,e.se.fi);
res.se.se=min(res.se.se,e.se.se);
}
if(R>mid){
auto e=query(now<<1|1,L,R);
res.fi+=e.fi;
res.se.fi=max(res.se.fi,e.se.fi);
res.se.se=min(res.se.se,e.se.se);
}
return res;
}
void addPath(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
update(1,id[top[x]],id[x]);
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
update(1,id[x]+1,id[y]);
}
pair<int,pair<int,int> > queryPath(int x,int y){
if(x==y)return mp(0,mp(0,0));
pair<int,pair<int,int> > res=mp(0,mp(-inf,inf));
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
auto e=query(1,id[top[x]],id[x]);
res.fi+=e.fi;
res.se.fi=max(res.se.fi,e.se.fi);
res.se.se=min(res.se.se,e.se.se);
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
auto e=query(1,id[x]+1,id[y]);
res.fi+=e.fi;
res.se.fi=max(res.se.fi,e.se.fi);
res.se.se=min(res.se.se,e.se.se);
return res;
}
pair<int,int>p[N];
void solve(){
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
x++;y++;
add(x,y,z);add(y,x,z);
p[i].fi=x;
p[i].se=y;
}
dfs1(1,1,0);
dfs2(1,1);
build(1,1,n);
scanf("%d",&m);
while(m--){
scanf("%s%d%d",&s,&x,&y);
x++;y++;
if(s[0]=='C'){
x--;y--;
int op=max(id[p[x].fi],id[p[x].se]);
updateVal(1,op,y);
}else if(s[0]=='N'){
addPath(x,y);
}else if(s[1]=='U'){
printf("%d\n",queryPath(x,y).fi);
}else if(s[1]=='A'){
printf("%d\n",queryPath(x,y).se.fi);
}else printf("%d\n",queryPath(x,y).se.se);
}
}
int main(){
//int o;scanf("%d",&o);
//while(o--){
solve();
//}
return 0;
}
[HNOI2016]网络
个人感觉这道题是这个专题里面最难的一题了,也是唯一一道没有做出来的题。这道题首先要转换一下思路,当某一条路径上出现一条交互信息时,可以把这条交互信息的重要程度放到不在这条路径上的其它点中(这个可以在树剖时记录每一条链的左右端点,然后进行排序,位于端点间的线段就是需要更新的区间),因为你修改其它不在这条路径上的点时并不会影响这道路径上重要程度(信息交互结束同理,相当于在其他节点上去掉该重要程度),所以当某一个服务器坏了之后,就是看这个服务器上最大的没有被删除的重要程度是什么。所以我们要维护一个堆,当某一条路径上出现一个交互时,就把不在这条路径上的所有区间都加入该重要程度,由于还有撤销操作,所以我们还要再维护一个堆,代表删除操作,当撤销时就把该值加入到撤销的堆中。在这里我们要明确一点就是在维护时,线段树时不需要下传和上传的,因为你查询的位置在你所在的区间内,那么该位置的更新肯定会影响该区间,也正是因为这个原因,线段树才能建两个堆,否则不仅时间不允许,空间也不允许。所以,查询时,我们只需要查询路径上的最大值即可,而且查询节点最大值时,首先要与删除堆的堆顶元素进行比较,若对顶元素相同,那么则要一起pop掉,因为该元素是撤销过的。
代码如下:
#include
#define all(x) (x).begin(),(x).end()
#define le(x) ((int)(x).size())
#define LL long long
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define db printf("Here!\n");
using namespace std;
const double eps=1e-6;
const LL inf=1e8;
const int N=2e5+5;
const int M=4e5+5;
const int mod=998244353;
int n,m,k,t,T,len,op,z,x,y;
int id[N],son[N],top[N],head[M],nxt[M],to[M],siz[N],f[N],dep[N],a[N],b[N];
char s[20];
struct node{
int l,r;
priority_queue<int>lc,rc;
int topV(){
while(!rc.empty()&&rc.top()==lc.top())lc.pop(),rc.pop();//比较两个堆顶元素同时弹出
return lc.empty()?-1:lc.top();
}
}q[N*4];
void add(int x,int y){nxt[++t]=head[x];head[x]=t;to[t]=y;}
void dfs1(int now,int fa){
f[now]=fa;
dep[now]=dep[fa]+1;
siz[now]=1;
int mx=0;
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa)continue;
dfs1(u,now);
siz[now]+=siz[u];
if(siz[u]>siz[mx])mx=u;
}
if(mx)son[now]=mx;
}
void dfs2(int now,int fa){
id[now]=++T;
top[now]=fa;
b[id[now]]=a[now];
if(!son[now])return;
dfs2(son[now],fa);
for(int i=head[now];i;i=nxt[i]){
int u=to[i];
if(u==fa||id[u])continue;
dfs2(u,u);
}
}
void build(int now,int l,int r){
q[now].l=l;q[now].r=r;
if(l==r){
return ;
}
int mid=l+r>>1;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
}
void update(int now,int L,int R,int val,int kind){
if(q[now].l>=L&&q[now].r<=R){
kind?q[now].lc.push(val):q[now].rc.push(val);
return;
}
int mid=q[now].l+q[now].r>>1;
if(mid>=L)update(now<<1,L,R,val,kind);
if(R>mid)update(now<<1|1,L,R,val,kind);
}
int query(int now,int pos){
if(q[now].l==pos&&q[now].r==pos){
return q[now].topV();
}
int res=q[now].topV();//不断求最大
int mid=q[now].l+q[now].r>>1;
if(mid>=pos)return max(res,query(now<<1,pos));
else return max(res,query(now<<1|1,pos));
}
pair<int,int>line[N];
void changePath(int x,int y,int z,int kind){
T=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
line[++T].fi=id[top[x]];
line[T].se=id[x];
x=f[top[x]];
}
if(id[x]>id[y])swap(x,y);
line[++T].fi=id[x];
line[T].se=id[y];
sort(line+1,line+1+T);
if(line[1].fi>1)update(1,1,line[1].fi-1,z,kind);
int mx=line[1].se;
for(int i=2;i<=T;i++){
if(line[i].fi>mx){
update(1,mx+1,line[i].fi-1,z,kind);
}
mx=max(mx,line[i].se);
}
if(n>mx)update(1,mx+1,n,z,kind);
}
pair<pair<int,int>,int>p[N];
void solve(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
dfs1(1,1);
dfs2(1,1);
build(1,1,n);
for(int i=1;i<=m;i++){
scanf("%d",&op);
if(!op){
scanf("%d%d%d",&x,&y,&z);
p[i].fi.fi=x;
p[i].fi.se=y;
p[i].se=z;
changePath(x,y,z,1);
}else if(op==2){
scanf("%d",&x);
printf("%d\n",query(1,id[x]));
}else {
scanf("%d",&x);
changePath(p[x].fi.fi,p[x].fi.se,p[x].se,0);
}
}
}
int main(){
//int o;scanf("%d",&o);
//while(o--){
solve();
//}
return 0;
}
下一个打算是刷一下基础数据结构题型,我觉得还是要多见一些题型的。最近也想了好多,虽然坚持下去不一定有结果,但是梦还是要有的,万一呢,作为头铁型选手,我不想给自己留遗憾,加油啦。