树链剖分好(du)题(liu)选做
1.luogu P4315 月下“毛景树”
前言:
这大概是本蒟蒻A掉的题里面码量最大的一道题了。我自认为码风比较紧凑,但还是写了175行。
从下午2点多调到晚上8点。中间小错不断。最后还是借助了郭神的AC代码。。
%%%stO郭神Orz%%%
还是我代码能力不够。以后要多写一些这样的题练练手。
解析:
题目相当裸。树链剖分+线段树维护区间最大值。
需要注意的点大致如下:
1.边权化点权
2.线段树需要实现的功能:区间加,区间赋值,区间查询最大值。
看起来貌似有手就行其实对于郭神这样的神犇确实有手就行,但是本蒟蒻调了一下午。。。
犯的其实都是一些低错,大概如下:
1.3个modify函数用串了
2.树链剖分跳top的时候,注意是 \(dfn[top[u]]<=dfn[u]\) ,因此应该是modifyfz(1,1,n,dfn[top[u]],dfn[u],w);
而不是modifyfz(1,1,n,dfn[u],dfn[top[u]],w);
3.注意在pushdown的时候要先判一下有没有区间赋值标记。
真正因为有理解上的问题而犯的错误:
在区间修改时要下传标记。每次更新前要保证该节点的状态一定是最新的,否则就会出错。
完整代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
int dfn[maxn],head[maxn],top[maxn],size[maxn],fa[maxn],w[maxn],son[maxn],depth[maxn],tree[maxn<<2],lazyfz[maxn<<2],lazyadd[maxn<<2];
int n,cnt,Time;
struct node{
int to,next,val;
}edge[maxn<<1];
struct Node{
int from,to;
}b[maxn];
void add(int from,int to,int val){
edge[++cnt].to=to;
edge[cnt].val=val;
edge[cnt].next=head[from];
head[from]=cnt;
}
void dfs1(int u,int f){
fa[u]=f;
size[u]=1;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==f) continue;
depth[v]=depth[u]+1;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t,int s){
top[u]=t;
dfn[u]=++Time;
w[Time]=s;
if(son[u]){
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==son[u]){
dfs2(v,t,edge[i].val);
break;
}
}
}
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v,edge[i].val);
}
}
void build(int rt,int l,int r){
lazyfz[rt]=-1;
if(l==r){
tree[rt]=w[l];
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
}
void updateadd(int rt,int w){
tree[rt]+=w;
lazyadd[rt]+=w;
}
void updatefz(int rt,int w){
tree[rt]=lazyfz[rt]=w;
lazyadd[rt]=0;
}
void pushdown(int rt){
if(lazyfz[rt]!=-1){
updatefz(rt<<1,lazyfz[rt]);
updatefz(rt<<1|1,lazyfz[rt]);
lazyfz[rt]=-1;
}
if(lazyadd[rt]){
updateadd(rt<<1,lazyadd[rt]);
updateadd(rt<<1|1,lazyadd[rt]);
lazyadd[rt]=0;
}
}
void modifyfz(int rt,int l,int r,int s,int t,int w){
if(s<=l&&r<=t){
updatefz(rt,w);
return;
}
int mid=(l+r)>>1;
pushdown(rt);
if(s<=mid) modifyfz(rt<<1,l,mid,s,t,w);
if(t>mid) modifyfz(rt<<1|1,mid+1,r,s,t,w);
tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
}
void Cover(int u,int v,int w){
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
modifyfz(1,1,n,dfn[top[u]],dfn[u],w);
u=fa[top[u]];
}
if(v==u) return;
if(depth[u]>depth[v]) swap(u,v);
modifyfz(1,1,n,dfn[u]+1,dfn[v],w);
}
void modifyadd(int rt,int l,int r,int s,int t,int w){
if(s<=l&&r<=t){
updateadd(rt,w);
return;
}
int mid=(l+r)>>1;
pushdown(rt);
if(s<=mid) modifyadd(rt<<1,l,mid,s,t,w);
if(t>mid) modifyadd(rt<<1|1,mid+1,r,s,t,w);
tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
}
void Modify(int u,int v,int w){
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
modifyadd(1,1,n,dfn[top[u]],dfn[u],w);
u=fa[top[u]];
}
if(u==v) return;
if(depth[u]>depth[v]) swap(u,v);
modifyadd(1,1,n,dfn[u]+1,dfn[v],w);
}
int query(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t) return tree[rt];
int mid=(l+r)>>1;
pushdown(rt);
if(t<=mid) return query(rt<<1,l,mid,s,t);
if(s>mid) return query(rt<<1|1,mid+1,r,s,t);
return max(query(rt<<1,l,mid,s,t),query(rt<<1|1,mid+1,r,s,t));
}
int Query(int u,int v){
int res=-1e9;
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
res=max(res,query(1,1,n,dfn[top[u]],dfn[u]));
u=fa[top[u]];
}
if(u==v) return res;
if(depth[u]>depth[v]) swap(u,v);
res=max(res,query(1,1,n,dfn[u]+1,dfn[v]));
return res;
}
void Solve(){
scanf("%d",&n);
for(int i=1,z;i<n;++i){
scanf("%d%d%d",&b[i].from,&b[i].to,&z);
add(b[i].from,b[i].to,z);
add(b[i].to,b[i].from,z);
}
dfs1(1,0);
dfs2(1,1,0);
build(1,1,n);
char ccc[10];
int x,y,z;
for(;;){
scanf("%s",ccc);
if(ccc[0]=='S') return;
if(ccc[1]=='h'){
scanf("%d%d",&x,&y);
int d=max(dfn[b[x].from],dfn[b[x].to]);
modifyfz(1,1,n,d,d,y);
}else if(ccc[1]=='o'){
scanf("%d%d%d",&x,&y,&z);
Cover(x,y,z);
}else if(ccc[1]=='d'){
scanf("%d%d%d",&x,&y,&z);
Modify(x,y,z);
}else{
scanf("%d%d",&x,&y);
printf("%d\n",Query(x,y));
}
}
}
int main(){
Solve();
return 0;
}
2.luogu P1505 [国家集训队]旅游
前言:
又是一道毒瘤题。代码201行。不过有了上一道题的经验之后就好解决多了。然而调试还是花了将近2小时
解析:
其实还是一道裸题。
这次线段树的功能有:区间变相反数,区间查询最大、最小值,区间求和,单点赋值。
需要注意的点:
1.因为有区间变相反数,因此pushdown时需要把最大值变成原来最小值的相反数,最小值同理。
然后我就把pushdown写成了这样:
void update(int rt){
treesum[rt]*=-1;
treemax[rt]=treemin[rt]*(-1);
treemin[rt]=treemax[rt]*(-1);
lazy[rt]*=-1;
}
void pushdown(int rt){
if(lazy[rt]==1) return;
update(rt<<1);
update(rt<<1|1);
lazy[rt]=1;
}
然后就挂了。
所以其实就是交换两个整数,显然不能直接换,所以要加一个中间变量
然后就写成这样:
void update(int rt){
treesum[rt]*=-1;
int x=treemax[rt];
treemax[rt]=treemin[rt]*(-1);
treemin[rt]=x*(-1);
lazy[rt]*=-1;
}
具体在调试过程中,还有一个理解上的问题。
int querysum(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t){
//这里是不需要pushdown的。
return treesum[rt];
}
int mid=(l+r)>>1;
pushdown(rt);
if(t<=mid) return querysum(rt<<1,l,mid,s,t);
if(s>mid) return querysum(rt<<1|1,mid+1,r,s,t);
return querysum(rt<<1,l,mid,s,t)+querysum(rt<<1|1,mid+1,r,s,t);
}
因为除了我全世界都知道,线段树的lazy标记是用来标记该节点的子节点的,跟这个节点本身没有关系。
完整代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
struct node{
int to,next,val;
}edge[maxn<<1];
int lazy[maxn<<2],treesum[maxn<<2],a[maxn],w[maxn],treemax[maxn<<2],treemin[maxn<<2],size[maxn],top[maxn],son[maxn],depth[maxn],fa[maxn],head[maxn],dfn[maxn];
struct Node{
int from,to;
}b[maxn];
int n,m,x,y,z,o,Time,cnt;
char ccc[7];
void add(int from,int to,int val){
edge[++cnt].to=to;
edge[cnt].val=val;
edge[cnt].next=head[from];
head[from]=cnt;
}
void pushup(int rt){
treesum[rt]=treesum[rt<<1]+treesum[rt<<1|1];
treemax[rt]=max(treemax[rt<<1],treemax[rt<<1|1]);
treemin[rt]=min(treemin[rt<<1],treemin[rt<<1|1]);
}
void build(int rt,int l,int r){
lazy[rt]=1;
if(l==r){
treesum[rt]=treemax[rt]=treemin[rt]=w[l];
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
void dfs1(int u,int f){
fa[u]=f;
size[u]=1;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==f) continue;
depth[v]=depth[u]+1;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t,int s){
top[u]=t;
dfn[u]=++Time;
w[Time]=s;
if(son[u]){
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==son[u]){
dfs2(v,t,edge[i].val);
break;
}
}
}
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v,edge[i].val);
}
}
void update(int rt){
treesum[rt]*=-1;
int x=treemax[rt];
treemax[rt]=treemin[rt]*(-1);
treemin[rt]=x*(-1);
lazy[rt]*=-1;
}
void pushdown(int rt){
if(lazy[rt]==1) return;
update(rt<<1);
update(rt<<1|1);
lazy[rt]=1;
}
void modify(int rt,int l,int r,int x,int k){
if(l==r){
treesum[rt]=treemax[rt]=treemin[rt]=k;
lazy[rt]=1;
return;
}
int mid=(l+r)>>1;
pushdown(rt);
if(x<=mid) modify(rt<<1,l,mid,x,k);
else modify(rt<<1|1,mid+1,r,x,k);
pushup(rt);
}
void modify2(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t){
update(rt);
return;
}
pushdown(rt);
int mid=(l+r)>>1;
if(s<=mid) modify2(rt<<1,l,mid,s,t);
if(t>mid) modify2(rt<<1|1,mid+1,r,s,t);
pushup(rt);
}
void Modify(int u,int v){
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
modify2(1,1,n,dfn[top[u]],dfn[u]);
u=fa[top[u]];
}
if(u==v) return;
if(depth[u]>depth[v]) swap(u,v);
modify2(1,1,n,dfn[u]+1,dfn[v]);
}
int querymax(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t) return treemax[rt];
int mid=(l+r)>>1;
pushdown(rt);
if(t<=mid) return querymax(rt<<1,l,mid,s,t);
if(s>mid) return querymax(rt<<1|1,mid+1,r,s,t);
return max(querymax(rt<<1,l,mid,s,t),querymax(rt<<1|1,mid+1,r,s,t));
}
int Querymax(int u,int v){
int res=-1e9;
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
res=max(res,querymax(1,1,n,dfn[top[u]],dfn[u]));
u=fa[top[u]];
}
if(u==v) return res;
if(depth[u]>depth[v]) swap(u,v);
res=max(res,querymax(1,1,n,dfn[u]+1,dfn[v]));
return res;
}
int querymin(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t) return treemin[rt];
int mid=(l+r)>>1;
pushdown(rt);
if(t<=mid) return querymin(rt<<1,l,mid,s,t);
if(s>mid) return querymin(rt<<1|1,mid+1,r,s,t);
return min(querymin(rt<<1,l,mid,s,t),querymin(rt<<1|1,mid+1,r,s,t));
}
int Querymin(int u,int v){
int res=0x7fffffff;
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
res=min(res,querymin(1,1,n,dfn[top[u]],dfn[u]));
u=fa[top[u]];
}
if(u==v) return res;
if(depth[u]>depth[v]) swap(u,v);
res=min(res,querymin(1,1,n,dfn[u]+1,dfn[v]));
return res;
}
int querysum(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t) return treesum[rt];
int mid=(l+r)>>1;
pushdown(rt);
if(t<=mid) return querysum(rt<<1,l,mid,s,t);
if(s>mid) return querysum(rt<<1|1,mid+1,r,s,t);
return querysum(rt<<1,l,mid,s,t)+querysum(rt<<1|1,mid+1,r,s,t);
}
int Querysum(int u,int v){
int res=0;
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
res+=querysum(1,1,n,dfn[top[u]],dfn[u]);
u=fa[top[u]];
}
if(u==v) return res;
if(depth[u]>depth[v]) swap(u,v);
res+=querysum(1,1,n,dfn[u]+1,dfn[v]);
return res;
}
void Solve(){
scanf("%d",&n);
for(int i=1;i<n;++i){
scanf("%d%d%d",&b[i].from,&b[i].to,&z);
b[i].from++;
b[i].to++;
add(b[i].from,b[i].to,z);
add(b[i].to,b[i].from,z);
}
dfs1(1,0);
dfs2(1,1,0);
build(1,1,n);
scanf("%d",&m);
for(int i=1;i<=m;++i){
scanf("%s%d%d",ccc,&x,&y);
if(ccc[0]=='C'){
o=max(dfn[b[x].from],dfn[b[x].to]);
modify(1,1,n,o,y);
}
x++;
y++;
if(ccc[0]=='N') Modify(x,y);
else if(ccc[0]=='S') printf("%d\n",Querysum(x,y));
else if(ccc[1]=='A') printf("%d\n",Querymax(x,y));
else if(ccc[1]=='I') printf("%d\n",Querymin(x,y));
}
}
int main(){
Solve();
return 0;
}
3.luogu P2146 [NOI2015]软件包管理器
前言:
这道题其实并不难,码量也就100行。不过我刚开始做的时候把问题想复杂了,有一些点没想清楚,就导致写了很长时间。因为码量较小,其实调试时间并不长。
解析:
其实还是一道裸题。
可以发现安装一个包就是把这个点到根节点的路径里所有的点都变成1,卸载一个包就是把这个点的子树里面所有的点都变成0。
其实树剖维护子树的题好像挺少的,我目前只见过这个题和P3384 【模板】轻重链剖分 里面涉及到这个操作。可能是因为这个太简单了。因为一棵子树中的dfs序是连续的,所以很好处理。
但是sbwzx在刚开始的时候竟然没想到。。。因为脑子比较混乱,就错误地把卸载一个包当成把这个点和根节点里面所有的点都变成0了,然后惊喜地发现连样例都过不去。
最后还是看了一眼题解才想明白,wtcl。这种sb错误全世界应该只有我一个人犯了吧。
这次线段树的功能有:区间赋值,查询区间和。
需要注意的点:貌似没啥,挺好写的
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100000+10;
int head[maxn],tree[maxn<<2],top[maxn],depth[maxn],size[maxn],dfn[maxn],fa[maxn],son[maxn],lazy[maxn<<2];
int n,cnt,Time,q;
struct node{
int to,next;
}edge[maxn<<1];
ll ans;
void add(int from,int to){
edge[++cnt].to=to;
edge[cnt].next=head[from];
head[from]=cnt;
}
void dfs1(int u){
size[u]=1;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==fa[u]) continue;
depth[v]=depth[u]+1;
dfs1(v);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t){
top[u]=t;
dfn[u]=++Time;
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
void update(int rt,int l,int r,int w){
lazy[rt]=w;
tree[rt]=w*(r-l+1);
}
void pushdown(int rt,int l,int r){
if(lazy[rt]!=-1){
int mid=(l+r)>>1;
update(rt<<1,l,mid,lazy[rt]);
update(rt<<1|1,mid+1,r,lazy[rt]);
lazy[rt]=-1;
}
}
void modify(int rt,int l,int r,int s,int t,int w){
if(s<=l&&r<=t){
update(rt,l,r,w);
return;
}
int mid=(l+r)>>1;
pushdown(rt,l,r);
if(s<=mid) modify(rt<<1,l,mid,s,t,w);
if(t>mid) modify(rt<<1|1,mid+1,r,s,t,w);
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
int query(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t) return tree[rt];
int mid=(l+r)>>1;
pushdown(rt,l,r);
if(t<=mid) return query(rt<<1,l,mid,s,t);
if(s>mid) return query(rt<<1|1,mid+1,r,s,t);
return query(rt<<1,l,mid,s,t)+query(rt<<1|1,mid+1,r,s,t);
}
int Query0(int y){
int res=query(1,1,n,dfn[y],dfn[y]+size[y]-1);
modify(1,1,n,dfn[y],dfn[y]+size[y]-1,0);
return res;
}
int Query1(int y){
int res=0;
while(top[y]!=1){
res+=dfn[y]-dfn[top[y]]+1-query(1,1,n,dfn[top[y]],dfn[y]);
modify(1,1,n,dfn[top[y]],dfn[y],1);
y=fa[top[y]];
}
res+=dfn[y]-query(1,1,n,1,dfn[y]);
modify(1,1,n,1,dfn[y],1);
return res;
}
void Solve(){
scanf("%d",&n);
for(int i=2;i<=n;++i){
scanf("%d",&fa[i]);
fa[i]++;
add(i,fa[i]);
add(fa[i],i);
}
dfs1(1);
dfs2(1,1);
memset(lazy,-1,sizeof(lazy));
scanf("%d",&q);
char ccc[20];
for(int i=1,x;i<=q;++i){
scanf("%s%d",ccc,&x);
x++;
if(ccc[0]=='i') printf("%d\n",Query1(x));
else printf("%d\n",Query0(x));
}
}
int main(){
Solve();
return 0;
}
4.luogu P4281 [AHOI2008]紧急集合 / 聚会
前言:
这题就带一点思维了。我第一眼做的时候以为是个码力题,对题目最关键的性质并没有看清楚,最后看了题解才明白。思维力太差,自己做题的时候都不愿意去想太多,可能是我最近做不用脑子的题太多了,要好好反思一下。
解析:
首先一眼看去,前置知识显然只有树剖求Lca,关键就是怎么用到这个题里面。
因为是三个点,所以对我来说并不是很显然的样子。
我们的目标是找到树上某一点,使这个点到树上某三个点的距离之和最小。
两个点的话,显然是Lca,那么三个点呢?
首先我想到的是三个点的Lca,但是等我写出来之后连样例都过不了。
后来我一看,这显然是错的啊,即使是这样我也还是傻傻地写了出来
考虑三个点如果在同一条链上,那么最后答案一定是中间的那个点,那显然不是Lca。
当我想到这里的时候人就傻了,然后就直接去看题解。看完结论才发现如果自己好好想想应该也能想出来,唉。
结论:这个点是三个两两之间的Lca中,深度最大的那个点。
证明:其实想到这个以后就挺显然了
我们把要求的三个点称为\(u,v,w\) ,正确答案称为\(x\);
1.假设三个点在同一条链上,显然正确。
2.假设其中一个点(不妨设为w)与另外两点(不妨设为u,v)不在一条链上。
(设u是v的祖先)
假设w的深度小于v,那么答案就是Lca(w,v);
假设w的深度大于v,那么不妨交换w,v,Lca(w,v)不变,还是对的
(写的有点绕?其实感觉只要想到这个性质,后面证明就挺显然的)
代码就相当好写了,写完后甚至不用调一遍AC:
#include <bits/stdc++.h>
using namespace std;
const int maxn=500000+10;
struct node{
int to,next;
}edge[maxn<<1];
int head[maxn],top[maxn],size[maxn],son[maxn],fa[maxn],depth[maxn];
int n,m,cnt,ans;
void add(int from,int to){
edge[++cnt].to=to;
edge[cnt].next=head[from];
head[from]=cnt;
}
void dfs1(int u,int f){
size[u]=1;
fa[u]=f;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==f) continue;
depth[v]=depth[u]+1;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t){
top[u]=t;
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
int Lca(int u,int v){
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
u=fa[top[u]];
}
return depth[u]<depth[v] ? u : v ;
}
int query(int u,int v){
int res=0;
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
res+=depth[u]-depth[top[u]]+1;
u=fa[top[u]];
}
res+=abs(depth[u]-depth[v]);
return res;
}
int Query(int u,int v,int w){
ans=0;
int l1=Lca(u,v);
int x=l1;
int l2=Lca(u,w);
x=depth[x] > depth[l2] ? x : l2 ;
int l3=Lca(v,w);
x=depth[x] > depth[l3] ? x : l3 ;
ans+=query(u,x);
ans+=query(v,x);
ans+=query(w,x);
return x;
}
void Solve(){
scanf("%d%d",&n,&m);
for(int i=1,x,y;i<n;++i){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,1);
for(int i=1,x,y,z;i<=m;++i){
scanf("%d%d%d",&x,&y,&z);
int tool=Query(x,y,z);
printf("%d %d\n",tool,ans);
}
}
int main(){
Solve();
return 0;
}
5.luogu P2486 [SDOI2011]染色
前言:
第一次想做这道题还是在小集训的时候,我刚开始学树剖。当时觉得不是很好维护于是就弃了。最近又拿出来,感觉并不难。这道题分了很多时间段写,总体的解决时间很短,也算是一个进步吧。
解析:
主要问题在线段树上。因为要维护的是区间连续段的个数,所以不是很板子。
想一下应该还是比较好实现的。
线段树维护区间连续段的个数,再记录赋值标记。
因为要方便合并,所以还要维护这个区间最左边和最右边的颜色编号是多少。
然后好像就没了,注意细节吧。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=500000+10;
struct node{
int to,next;
}edge[maxn<<1];
int head[maxn],top[maxn],size[maxn],son[maxn],fa[maxn],depth[maxn],dfn[maxn],a[maxn],w[maxn];
int n,m,cnt,Time;
struct Segment_tree{
int val,lazy,lc,rc;//lc,rc分别表示这个区间左右端点的color,lazy==-1时表示没有赋值
}tree[maxn<<2];
void add(int from,int to){
edge[++cnt].to=to;
edge[cnt].next=head[from];
head[from]=cnt;
}
void dfs1(int u,int f){
size[u]=1;
fa[u]=f;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==f) continue;
depth[v]=depth[u]+1;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t){
top[u]=t;
dfn[u]=++Time;
w[Time]=a[u];
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
void pushup(int rt){
tree[rt].lc=tree[rt<<1].lc;
tree[rt].rc=tree[rt<<1|1].rc;
tree[rt].val=tree[rt<<1].val+tree[rt<<1|1].val;
if(tree[rt<<1].rc==tree[rt<<1|1].lc) tree[rt].val--;
}
void update(int rt,int x){
tree[rt].lazy=x;
tree[rt].val=1;
tree[rt].lc=tree[rt].rc=x;
}
void pushdown(int rt){
if(tree[rt].lazy==-1) return;
tree[rt].val=1;
update(rt<<1,tree[rt].lazy);
update(rt<<1|1,tree[rt].lazy);
tree[rt].lazy=-1;
}
void modify(int rt,int l,int r,int s,int t,int p){
if(s<=l&&r<=t){
update(rt,p);
return;
}
int mid=(l+r)>>1;
pushdown(rt);
if(s<=mid) modify(rt<<1,l,mid,s,t,p);
if(t>mid) modify(rt<<1|1,mid+1,r,s,t,p);
pushup(rt);
}
void build(int rt,int l,int r){
tree[rt].lazy=-1;
if(l==r){
tree[rt].val=1;
tree[rt].lc=tree[rt].rc=w[l];
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
int query2(int rt,int l,int r,int x){
if(l==r) return tree[rt].lc;
int mid=(l+r)>>1;
pushdown(rt);
if(x<=mid) return query2(rt<<1,l,mid,x);
else return query2(rt<<1|1,mid+1,r,x);
}
int query(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t) return tree[rt].val;
int mid=(l+r)>>1;
pushdown(rt);
if(t<=mid) return query(rt<<1,l,mid,s,t);
if(s>mid) return query(rt<<1|1,mid+1,r,s,t);
return query(rt<<1,l,mid,s,t)+query(rt<<1|1,mid+1,r,s,t)-(tree[rt<<1].rc==tree[rt<<1|1].lc);
}
int Query(int u,int v){
int res=0;
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
res+=query(1,1,n,dfn[top[u]],dfn[u]);
if(top[u]!=1 && query2(1,1,n,dfn[top[u]])==query2(1,1,n,dfn[fa[top[u]]])) res--;
u=fa[top[u]];
}
if(depth[u]>depth[v]) swap(u,v);
res+=query(1,1,n,dfn[u],dfn[v]);
return res;
}
void Modify(int u,int v,int x){
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
modify(1,1,n,dfn[top[u]],dfn[u],x);
u=fa[top[u]];
}
if(depth[u]>depth[v]) swap(u,v);
modify(1,1,n,dfn[u],dfn[v],x);
}
void Solve(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1,x,y;i<n;++i){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,1);
build(1,1,n);
char ccc[5];
for(int i=1,x,y,z;i<=m;++i){
scanf("%s%d%d",ccc,&x,&y);
if(ccc[0]=='Q') printf("%d\n",Query(x,y));
else{
scanf("%d",&z);
Modify(x,y,z);
}
}
}
int main(){
Solve();
return 0;
}
update 强烈谴责skyh题表里面的树链剖分难度!太水了! by 2020.10.12
S(A)kyh在luogu里面放了个数据结构题表,于是我去刷了一下里面的树剖题。
@skyh
6.luogu P4114 Qtree1
前言:
太水了没啥可说的,直接切了
解析:
无
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
int dfn[maxn],head[maxn],top[maxn],size[maxn],fa[maxn],son[maxn],depth[maxn],tree[maxn<<2],w[maxn];
struct node{
int to,next,val;
}edge[maxn<<1];
struct Node{
int from,to;
}b[maxn];
int cnt,n,Time;
void add(int from,int to,int val){
edge[++cnt].to=to;
edge[cnt].val=val;
edge[cnt].next=head[from];
head[from]=cnt;
}
void dfs1(int u,int f){
size[u]=1;
fa[u]=f;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==f) continue;
depth[v]=depth[u]+1;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t,int s){
top[u]=t;
dfn[u]=++Time;
w[Time]=s;
if(son[u]){
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==son[u]){
dfs2(v,t,edge[i].val);
break;
}
}
}
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v,edge[i].val);
}
}
void build(int rt,int l,int r){
if(l==r){
tree[rt]=w[l];
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
}
int query(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t) return tree[rt];
int mid=(l+r)>>1;
if(t<=mid) return query(rt<<1,l,mid,s,t);
if(s>mid) return query(rt<<1|1,mid+1,r,s,t);
return max(query(rt<<1,l,mid,s,t),query(rt<<1|1,mid+1,r,s,t));
}
int Query(int u,int v){
if(u==v) return 0;
int res=-0x3f3f3f3f;
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
res=max(res,query(1,1,n,dfn[top[u]],dfn[u]));
u=fa[top[u]];
}
if(u==v) return res;
if(depth[u]>depth[v]) swap(u,v);
res=max(res,query(1,1,n,dfn[u]+1,dfn[v]));
return res;
}
void modify(int rt,int l,int r,int x,int p){
if(l==r){
tree[rt]=p;
return;
}
int mid=(l+r)>>1;
if(x<=mid) modify(rt<<1,l,mid,x,p);
else modify(rt<<1|1,mid+1,r,x,p);
tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
}
void Solve(){
scanf("%d",&n);
for(int i=1,z;i<n;++i){
scanf("%d%d%d",&b[i].from,&b[i].to,&z);
add(b[i].from,b[i].to,z);
add(b[i].to,b[i].from,z);
}
dfs1(1,0);
dfs2(1,1,0);
build(1,1,n);
char ccc[10];
int x,y;
while(1){
scanf("%s",ccc);
if(ccc[0]=='D') return;
scanf("%d%d",&x,&y);
if(ccc[0]=='C'){
int s=max(dfn[b[x].from],dfn[b[x].to]);
modify(1,1,n,s,y);
}else printf("%d\n",Query(x,y));
}
}
int main(){
Solve();
return 0;
}
6.luogu P3833 [SHOI2012]魔法树
前言:
这个就更水了
解析:
无
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100000+10;
struct Segment_tree{
ll val,lazy;
}tree[maxn<<2];
int head[maxn],dfn[maxn],fa[maxn],size[maxn],son[maxn],depth[maxn],top[maxn];
struct node{
int to,next;
}edge[maxn<<1];
int n,cnt,q,Time;
void add(int from,int to){
edge[++cnt].to=to;
edge[cnt].next=head[from];
head[from]=cnt;
}
void dfs1(int u){
size[u]=1;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==fa[u]) continue;
depth[v]=depth[u]+1;
dfs1(v);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t){
top[u]=t;
dfn[u]=++Time;
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
void update(int rt,int l,int r,int d){
tree[rt].val+=1ll*d*(r-l+1);
tree[rt].lazy+=d;
}
void pushdown(int rt,int l,int r){
if(tree[rt].lazy==0) return;
int mid=(l+r)>>1;
update(rt<<1,l,mid,tree[rt].lazy);
update(rt<<1|1,mid+1,r,tree[rt].lazy);
tree[rt].lazy=0;
}
void modify(int rt,int l,int r,int s,int t,int d){
if(s<=l&&r<=t){
update(rt,l,r,d);
return;
}
int mid=(l+r)>>1;
pushdown(rt,l,r);
if(s<=mid) modify(rt<<1,l,mid,s,t,d);
if(t>mid) modify(rt<<1|1,mid+1,r,s,t,d);
tree[rt].val=tree[rt<<1].val+tree[rt<<1|1].val;
}
void Modify(int u,int v,int d){
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
modify(1,1,n,dfn[top[u]],dfn[u],d);
u=fa[top[u]];
}
if(depth[u]>depth[v]) swap(u,v);
modify(1,1,n,dfn[u],dfn[v],d);
}
ll query(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t) return tree[rt].val;
int mid=(l+r)>>1;
pushdown(rt,l,r);
if(t<=mid) return query(rt<<1,l,mid,s,t);
if(s>mid) return query(rt<<1|1,mid+1,r,s,t);
return query(rt<<1,l,mid,s,t)+query(rt<<1|1,mid+1,r,s,t);
}
void Solve(){
scanf("%d",&n);
for(int i=1,x,y;i<n;++i){
scanf("%d%d",&x,&y);
x++;
y++;
add(x,y);
add(y,x);
fa[y]=x;
}
dfs1(1);
dfs2(1,1);
scanf("%d",&q);
char ccc[5];
for(int i=1,u,v,d;i<=q;++i){
scanf("%s%d",ccc,&u);
u++;
if(ccc[0]=='A'){
scanf("%d%d",&v,&d);
v++;
Modify(u,v,d);
}
else printf("%lld\n",query(1,1,n,dfn[u],dfn[u]+size[u]-1));
}
}
int main(){
Solve();
return 0;
}
7.luogu P4427 [BJOI2018]求和
前言:
因为最近考试经常被爆踩,心情不爽就多写了两道树剖。像这种简单题大概能做到写代码不超过25分钟,调试不超过10分钟。虽然不是很快,不过也算可以了。
解析:
这个题不是很板子。不过一看k的范围就知道可以随便搞了。开50颗线段树,分别维护k次方和。然后普通的线段树求区间和即可。
代码:(好像跑的巨tm慢。。。)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=300000+10,mod=998244353;
struct node{
int to,next;
}edge[maxn<<1];
int head[maxn],size[maxn],fa[maxn],depth[maxn],top[maxn],dfn[maxn],son[maxn],w[maxn];
int n,cnt,Time,m;
ll tree[maxn<<2][60];
void add(int from,int to){
edge[++cnt].to=to;
edge[cnt].next=head[from];
head[from]=cnt;
}
void dfs1(int u,int f){
fa[u]=f;
size[u]=1;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==f) continue;
depth[v]=depth[u]+1;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t){
top[u]=t;
dfn[u]=++Time;
w[Time]=depth[u];
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
void pushup(int rt){
for(int i=1;i<=50;++i) tree[rt][i]=(tree[rt<<1][i]+tree[rt<<1|1][i])%mod;
}
void build(int rt,int l,int r){
if(l==r){
tree[rt][1]=w[l];
for(int i=2;i<=50;++i) tree[rt][i]=tree[rt][i-1]*w[l]%mod;
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
ll query(int rt,int l,int r,int s,int t,int k){
if(s<=l&&r<=t) return tree[rt][k];
int mid=(l+r)>>1;
if(t<=mid) return query(rt<<1,l,mid,s,t,k);
if(s>mid) return query(rt<<1|1,mid+1,r,s,t,k);
return (query(rt<<1,l,mid,s,t,k)+query(rt<<1|1,mid+1,r,s,t,k))%mod;
}
ll Query(int u,int v,int k){
ll res=0;
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
res+=query(1,1,n,dfn[top[u]],dfn[u],k);
if(res>=mod) res-=mod;
u=fa[top[u]];
}
if(depth[u]>depth[v]) swap(u,v);
res+=query(1,1,n,dfn[u],dfn[v],k);
if(res>=mod) res-=mod;
return res;
}
void Solve(){
scanf("%d",&n);
for(int i=1,x,y;i<n;++i){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,1);
build(1,1,n);
scanf("%d",&m);
for(int i=1,x,y,z;i<=m;++i){
scanf("%d%d%d",&x,&y,&z);
printf("%lld\n",Query(x,y,z));
}
}
int main(){
Solve();
return 0;
}
8.luogu P4092 [HEOI2016/TJOI2016]树
前言:
这个是用线段树上二分维护。也不是很板子,不过也很好写。
解析:
线段树上二分。大概思路就是先查右区间,如果右区间查不到,就查左区间。我感觉有点不对劲的地方就是查询的复杂度。。。不过貌似跑的挺快。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
int tree[maxn<<2],head[maxn],dfn[maxn],dfn_l[maxn],top[maxn],son[maxn],fa[maxn],depth[maxn],size[maxn];
int n,q,cnt,Time;
struct node{
int to,next;
}edge[maxn<<1];
void add(int from,int to){
edge[++cnt].to=to;
edge[cnt].next=head[from];
head[from]=cnt;
}
void dfs1(int u,int f){
size[u]=1;
fa[u]=f;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==f) continue;
depth[v]=depth[u]+1;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t){
top[u]=t;
dfn[u]=++Time;
dfn_l[Time]=u;
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
void modify(int rt,int l,int r,int pos){
if(l==r){
tree[rt]=1;
return;
}
int mid=(l+r)>>1;
if(pos<=mid) modify(rt<<1,l,mid,pos);
else modify(rt<<1|1,mid+1,r,pos);
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
int query(int rt,int l,int r,int s,int t){
if(tree[rt]==0) return 0;
if(l==r) return l;
int mid=(l+r)>>1;
int res=0;
if(t>mid) res=query(rt<<1|1,mid+1,r,s,t);
if(res) return res;
if(s<=mid) res=query(rt<<1,l,mid,s,t);
return res;
}
int Query(int u){
int res;
while(top[u]!=1){
res=query(1,1,n,dfn[top[u]],dfn[u]);
if(res!=0) return res;
u=fa[top[u]];
}
res=query(1,1,n,1,dfn[u]);
return res;
}
void Solve(){
scanf("%d%d",&n,&q);
for(int i=1,x,y;i<n;++i){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,1);
modify(1,1,n,1);
char ccc[10];
for(int i=1,x;i<=q;++i){
scanf("%s%d",ccc,&x);
if(ccc[0]=='C') modify(1,1,n,dfn[x]);
else printf("%d\n",dfn_l[Query(x)]);
}
}
int main(){
Solve();
return 0;
}
9.luogu P2420 让我们异或吧
前言:
大水题。某天晚上9点36,没事干了,就敲了一个。正好赶在9点46的时候A掉。不错不错。
解析:
无
#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
int head[maxn],dfn[maxn],top[maxn],size[maxn],fa[maxn],son[maxn],depth[maxn],w[maxn];
int tree[maxn<<2];
struct node{
int to,next,val;
}edge[maxn<<1];
int n,m,cnt,Time;
void add(int from,int to,int val){
edge[++cnt].to=to;
edge[cnt].val=val;
edge[cnt].next=head[from];
head[from]=cnt;
}
void dfs1(int u,int f){
size[u]=1;
fa[u]=f;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==f) continue;
depth[v]=depth[u]+1;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t,int s){
top[u]=t;
dfn[u]=++Time;
w[Time]=s;
if(son[u]){
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==son[u]){
dfs2(v,t,edge[i].val);
break;
}
}
}
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v,edge[i].val);
}
}
void build(int rt,int l,int r){
if(l==r){
tree[rt]=w[l];
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
tree[rt]=tree[rt<<1]^tree[rt<<1|1];
}
int query(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t) return tree[rt];
int mid=(l+r)>>1;
if(t<=mid) return query(rt<<1,l,mid,s,t);
if(s>mid) return query(rt<<1|1,mid+1,r,s,t);
return query(rt<<1,l,mid,s,t)^query(rt<<1|1,mid+1,r,s,t);
}
int Query(int u,int v){
int res=0;
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
res^=query(1,1,n,dfn[top[u]],dfn[u]);
u=fa[top[u]];
}
if(u==v) return res;
if(depth[u]>depth[v]) swap(u,v);
res^=query(1,1,n,dfn[u]+1,dfn[v]);
return res;
}
void Solve(){
scanf("%d",&n);
for(int i=1,x,y,z;i<n;++i){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
dfs1(1,0);
dfs2(1,1,0);
build(1,1,n);
scanf("%d",&m);
for(int i=1,x,y;i<=m;++i){
scanf("%d%d",&x,&y);
printf("%d\n",Query(x,y));
}
}
int main(){
Solve();
return 0;
}
10.luogu P3038 [USACO11DEC]Grass Planting G
前言:
大水题。没想到我做这道题最大的限制是我的英语水平。犯了一些沙雕错误。没有上一道打的好。
解析:
无
#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
int top[maxn],depth[maxn],fa[maxn],son[maxn],head[maxn],size[maxn],dfn[maxn],w[maxn];
int cnt,n,m,Time;
struct node{
int to,next;
}edge[maxn<<1];
struct Segment_tree{
int val,lazy;
}tree[maxn<<2];
void add(int from,int to){
edge[++cnt].to=to;
edge[cnt].next=head[from];
head[from]=cnt;
}
void dfs1(int u,int f){
fa[u]=f;
size[u]=1;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==f) continue;
depth[v]=depth[u]+1;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t){
top[u]=t;
dfn[u]=++Time;
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
void update(int rt,int l,int r,int w){
tree[rt].lazy+=w;
tree[rt].val+=w*(r-l+1);
}
void pushdown(int rt,int l,int r){
if(tree[rt].lazy==0) return;
int mid=(l+r)>>1;
update(rt<<1,l,mid,tree[rt].lazy);
update(rt<<1|1,mid+1,r,tree[rt].lazy);
tree[rt].lazy=0;
}
void modify(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t){
update(rt,l,r,1);
return;
}
int mid=(l+r)>>1;
pushdown(rt,l,r);
if(s<=mid) modify(rt<<1,l,mid,s,t);
if(t>mid) modify(rt<<1|1,mid+1,r,s,t);
tree[rt].val=tree[rt<<1].val+tree[rt<<1|1].val;
}
void Modify(int u,int v){
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
modify(1,1,n,dfn[top[u]],dfn[u]);
u=fa[top[u]];
}
if(u==v) return;
if(depth[u]>depth[v]) swap(u,v);
modify(1,1,n,dfn[u]+1,dfn[v]);
}
int query(int rt,int l,int r,int x){
if(l==r) return tree[rt].val;
int mid=(l+r)>>1;
pushdown(rt,l,r);
if(x<=mid) return query(rt<<1,l,mid,x);
else return query(rt<<1|1,mid+1,r,x);
}
void Solve(){
scanf("%d%d",&n,&m);
for(int i=1,x,y;i<n;++i){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,1);
char ccc[5];
for(int i=1,x,y;i<=m;++i){
scanf("%s%d%d",ccc,&x,&y);
if(ccc[0]=='P') Modify(x,y);
else{
int s=max(dfn[x],dfn[y]);
printf("%d\n",query(1,1,n,s));
}
}
}
int main(){
Solve();
return 0;
}
11.luogu P3398 仓鼠找sugar
前言:
感觉是个树上的结论题吧,最大的障碍不是树剖。
解析:
首先有一个性质,如果树上两条路径 \((u,v)\) 与 \((x,y)\) 有交点,那么必有\(Lca(u,v)\)在路径\((x,y)\)上,或\(Lca(x,y)\)在路径\((u,v)\)上。
反过来说,如果\(Lca(u,v)\)不在路径\((x,y)\)上,且\(Lca(x,y)\)不在路径\((u,v)\)上,那么两条路径一定没有交点。
证明:
首先,设\(Lca(u,v)=w,Lca(x,y)=z\),且\(z\)不在\((u,v)\)上;
1.\(z\)在\(w\)上方(\(z\)的深度小于\(w\))
大概就是这个样子好丑的图
如果想让两条路径有交点,那么\(x,y\)其中一个点至少要在以\(w\)为根的子树上。
但是,如果\(x\)(或\(y\))在以\(w\)为根的子树上,必有\(w\)在\((x,z)\)或\((y,z)\)上,与我们的假设矛盾。
所以,如果\(z\)在\(w\)上方,两条路径一定没有交点。
2.\(z\)的深度等于\(w\)。
这个就很显然。首先为了满足前提,\(w\)一定不能等于\(z\)。
然后就会发现,\(w\)和\(z\)一定分处不同的子树中,那么显然无交点。
3.\(z\)的深度大于\(w\)
此时交换\((x,y)\)和\((u,v)\),变成第一种情况。
然后貌似就证完了?我们现在证明了“如果\(Lca(u,v)\)不在路径\((x,y)\)上,且\(Lca(x,y)\)不在路径\((u,v)\)上,那么两条路径一定没有交点。”
也就是说,“\(Lca(u,v)\)在路径\((x,y)\)上,或\(Lca(x,y)\)在路径\((u,v)\)上”,这是“两条路径有交点”的必要条件。
其次,只要“\(Lca(u,v)\)在路径\((x,y)\)上,或\(Lca(x,y)\)在路径\((u,v)\)上”,两条路径就一定有交点。(显然,交点至少有一个,就是LCA本身啊)
也就是说,“\(Lca(u,v)\)在路径\((x,y)\)上,或\(Lca(x,y)\)在路径\((u,v)\)上”,还是“两条路径有交点”的充分条件。
于是我们惊奇地发现,这两个条件竟然是等价的!
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
struct node{
int to,next;
}edge[maxn<<1];
int head[maxn],dfn[maxn],top[maxn],size[maxn],fa[maxn],son[maxn],depth[maxn],tree[maxn<<2];
int n,cnt,q,Time;
void add(int from,int to){
edge[++cnt].to=to;
edge[cnt].next=head[from];
head[from]=cnt;
}
void dfs1(int u,int f){
fa[u]=f;
size[u]=1;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==f) continue;
depth[v]=depth[u]+1;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t){
top[u]=t;
dfn[u]=++Time;
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
void build(int rt,int l,int r){
if(l==r){
tree[rt]=1;
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
tree[rt]=tree[rt<<1|1]+tree[rt<<1];
}
int Lca(int u,int v){
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
u=fa[top[u]];
}
if(depth[u]>depth[v]) swap(u,v);
return u;
}
int query(int rt,int l,int r,int s,int t){
if(s<=l&&r<=t) return tree[rt];
int mid=(l+r)>>1;
if(t<=mid) return query(rt<<1,l,mid,s,t);
if(s>mid) return query(rt<<1|1,mid+1,r,s,t);
return query(rt<<1,l,mid,s,t)+query(rt<<1|1,mid+1,r,s,t);
}
int Query(int u,int v){
int res=0;
while(top[u]!=top[v]){
if(depth[top[u]]<depth[top[v]]) swap(u,v);
res+=query(1,1,n,dfn[top[u]],dfn[u]);
u=fa[top[u]];
}
if(u==v) return res;
if(depth[u]>depth[v]) swap(u,v);
res+=query(1,1,n,dfn[u]+1,dfn[v]);
return res;
}
bool Judge(int u,int v,int w){//w是否在u,v路径上;
return Query(u,v)==Query(u,w)+Query(v,w) ;
}
bool get_ans(int a,int b,int c,int d){
int x=Lca(a,b);
if(Judge(c,d,x)) return 1;
x=Lca(c,d);
if(Judge(a,b,x)) return 1;
return 0;
}
void Solve(){
scanf("%d%d",&n,&q);
for(int i=1,x,y;i<n;++i){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,1);
build(1,1,n);
int a,b,c,d;
while(q--){
scanf("%d%d%d%d",&a,&b,&c,&d);
if(get_ans(a,b,c,d)) puts("Y");
else puts("N");
}
}
int main(){
Solve();
return 0;
}
树链剖分好(du)题(liu)选做的更多相关文章
- BZOJ 2157 旅行(树链剖分码农题)
写了5KB,1发AC... 题意:给出一颗树,支持5种操作. 1.修改某条边的权值.2.将u到v的经过的边的权值取负.3.求u到v的经过的边的权值总和.4.求u到v的经过的边的权值最大值.5.求u到v ...
- bzoj1036 [ZJOI2008]树的统计Count 树链剖分模板题
[ZJOI2008]树的统计Count Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成 一些操作: I. CHANGE u ...
- HDU 3966 Aragorn's Story(模板题)【树链剖分】+【线段树】
<题目链接> 题目大意: 给定一颗带点权的树,进行两种操作,一是给定树上一段路径,对其上每个点的点权增加或者减少一个数,二是对某个编号点的点权进行查询. 解题分析: 树链剖分的模板题,还不 ...
- bzoj-2243 2243: [SDOI2011]染色(树链剖分)
题目链接: 2243: [SDOI2011]染色 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 6267 Solved: 2291 Descript ...
- SPOJ QTREE 树链剖分
树链剖分的第一题,易懂,注意这里是边. #include<queue> #include<stack> #include<cmath> #include<cs ...
- 树链剖分 - BZOJ 1036: [ZJOI2008]树的统计Count
这是树链剖分的入门题,也是我学树链剖分的第一题. 树链剖分:就是把树中和线段树联系起来,求(u,v)路径中权值的最大值和其路径的权值和. 入门blog:http://blog.sina.com.cn/ ...
- hdu_5029_relief grain(树链剖分)
题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=5029 题意:给你一个树,然后给你两点,将这两点之间的点涂上颜色,问涂色最多的那个颜色是什么,如果数量相 ...
- 【BZOJ4196】【NOI2015】软件包管理器(树链剖分,线段树)
[BZOJ4196][NOI2015]软件包管理器 题面 题目描述 Linux用户和OSX用户一定对软件包管理器不会陌生.通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你 ...
- 【SHOI2012】魔法树(树链剖分,线段树)
[SHOI2012]魔法树 题面 BZOJ上找不到这道题目 只有洛谷上有.. 所以粘贴洛谷的题面 题解 树链剖分之后直接维护线段树就可以了 树链剖分良心模板题 #include<iostream ...
- 【CJOJ2440】大话西游(树链剖分)
题面 Description "大话西游" 是一个在中国非常流行的在线游戏, 由 NIE 公司开发和维护. 这个游戏来源于著名的小说<西游记> 和周星弛的电影, 游戏的 ...
随机推荐
- Linux内核下包过滤框架——iptables&netfilter
iptables & netfilter 1.简介 netfilter/iptables(下文中简称为iptables)组成Linux内核下的包过滤防火墙,完成封包过滤.封包重定向和网络地址转 ...
- lua中的随机数
Lua 生成随机数需要用到两个函数:math.randomseed(xx), math.random([n [, m]]) 1. math.randomseed(n) 接收一个整数 n 作为随机序列种 ...
- 【转载】linux 工作队列上睡眠的认识--不要在默认共享队列上睡眠
最近项目组做xen底层,我已经被完爆无数遍了,关键在于对内核.驱动这块不熟悉,导致分析xen代码非常吃力.于是准备细细的将 几本 linux 书籍慢慢啃啃. 正好看到LINUX内核设计与实现,对于内核 ...
- 机器学习——主成分分析(PCA)
1 前言 PCA(Principal Component Analysis)是一种常用的无监督学习方法,是一种常用的数据分析方法. PCA 通过利用 正交变换 把由 线性相关变量 表示的观测数据转换为 ...
- 手把手教你实现栈以及C#中Stack源码分析
定义 栈又名堆栈,是一种操作受限的线性表,仅能在表尾进行插入和删除操作. 它的特点是先进后出,就好比我们往桶里面放盘子,放的时候都是从下往上一个一个放(入栈),取的时候只能从上往下一个一个取(出栈), ...
- Linux系列(20) - shutdown
作用 用于关机或重启 例子 [shutdown -h 05:30]:设定凌晨05:30关机 [shutdown -h +30]:30分钟后关机 [shutdown -h now] 立即关机 [shut ...
- vm中安装win2012并安装hyper-V不支持嵌套
在虚拟机中安装win2012,并安装hyper-v提示: 无法安装hyper-v:虚拟机监控程序已经在运行 找到虚拟机目录下,用文本编辑器打开该系统的虚拟机配置文件(.vmx后缀),在配置文件末尾增加 ...
- SQL-关联查询【转】
T_A A表 T_B B标,id为表与表相关联的字段`创建相关表结构 CREATE TABLE Table_B( id INT(2), serNum VARCHAR(10) ); CREATE TAB ...
- Python-对Pcap文件进行处理,获取指定TCP流
通过对TCP/IP协议的学习,本人写了一个可以实现对PCAP文件中的IPV4下的TCP流提取,以及提取指定的TCP流,鉴于为了学习,没有采用第三方包解析pcap,而是对bytes流进行解析,其核心思想 ...
- DeDeCMS v5.7 漏洞复现
DedeCMS V5.7 漏洞复现 XSS漏洞 首先我们在首页要进行用户的注册以及登录 这里我们已经提前注册过了,登录即可 普通用户账号密码:root/passwd 管理员账号密码:admin/pik ...