bzoj2157旅游
题意:
给定有权树,支持单边权修改,路径边权取相反数,路径边权求和,路径边权求最大最小值。
题解:
用link-cut tree
link-cut tree与树链剖分有些类似,都是用某种数据结构维护树链。但也有很大差异:树链剖分是依据子树节点数确定轻重边,一经确定,不能更改,所以用相对静态的线段树维护,常数也较小。而link-cut tree是用来求解动态树问题的,它的链随时可以改变,因此也只能用动态的splay来维护,常数较大。以下简称lct
注意:本数据结构中splay是以节点在lct中的深度为关键字确定顺序的。lct中节点的fa有两个意思,当它不为自己所在splay的根结点时,fa表示其在splay中的父节点,当它为splay中的根节点时,fa表示这整条树链在lct中的父亲节点。
lct有三个基础操作:
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 20010
#define inc(i,j,k) for(int i=j;i<=k;i++)
#define INF 0x3fffffff
using namespace std; //general
int n,m;
void debug(){
n=n;
} //edge
struct e{int t,w,n;}; e es[N*]; int ess,g[N];
void pe(int f,int t,int w){es[++ess]=(e){t,w,g[f]}; g[f]=ess; es[++ess]=(e){f,w,g[t]}; g[t]=ess;} //splay
int fa[N]/*点的父亲,包括在splay中的父亲和所在splay的父亲*/,c[N][]/*splay中的儿子节点*/;
int sm[N],mx[N],mn[N],v[N],/*区间维护信息*/dt[N],dts/*中间过程用*/; bool tg[N]/*标记*/;
inline bool is_root(int x){return fa[x]==||(c[fa[x]][]!=x&&c[fa[x]][]!=x);}//判断是否为所在splay的根节点
void pushdown(int x){//标记下传。注:本程序中的标记表明本节点已更新子孙未更新
if(x==)return;
if(tg[x]){
int l=c[x][],r=c[x][]; tg[x]^=;
if(l){tg[l]^=; v[l]=-v[l]; sm[l]=-sm[l]; int t=mx[l]; mx[l]=-mn[l]; mn[l]=-t;}
if(r){tg[r]^=; v[r]=-v[r]; sm[r]=-sm[r]; int t=mx[r]; mx[r]=-mn[r]; mn[r]=-t;}
}
}
void update(int x){
if(x==)return;
int l=c[x][],r=c[x][]; sm[x]=v[x]+sm[l]+sm[r]; mx[x]=max(v[x],max(mx[l],mx[r])); mn[x]=min(v[x],min(mn[l],mn[r]));
}
void rotate(int x){//旋转
if(x==||is_root(x))return;
int a=fa[x],b=fa[fa[x]]; bool d=c[a][]==x,e=c[b][]==a;
if(! is_root(a))c[b][e]=x;//注意在调用is_root前不能动参数的fa[a]和c[fa[a]][0]c[fa[a]][1],不然会导致错误结果
if(c[x][!d])fa[c[x][!d]]=a;/*注意别漏*/ fa[x]=b; fa[a]=x; c[a][d]=c[x][!d]; c[x][!d]=a;//注意修改的相对顺序
update(a); update(x); if(! is_root(x))update(b);
}
void splay(int x){//伸展
if(x==)return;
dts=; int t=x;
while(! is_root(t))dt[++dts]=t,t=fa[t]; dt[++dts]=t;//将根结点到x的所有标记一次下传
while(dts)pushdown(dt[dts]),dts--;
while(! is_root(x)){
if(!is_root(fa[x])) (c[fa[x]][]==x)^(c[fa[fa[x]]][]==fa[x])?rotate(x):rotate(fa[x]);
rotate(x);
}
} //lct
int access(int x){//lct基本操作,使节点到根节点连成一条链,并把链上的分支断开
if(x==)return ;
int t=;
while(x){
splay(x); c[x][]=t; if(t)fa[t]=x;
update(x); t=x; x=fa[x];
}
debug();
return t;
}
void link(int x,int y){//lct基本操作,本程序不用
if(x==||y==)return;
access(x); splay(x); fa[x]=y;
}
void cut(int x){//lct基本操作,本程序不用
if(x==)return;
access(x); splay(x); if(c[x][])fa[c[x][]]=; c[x][]=; update(x);
} //command
//注意修改时要根据题目要求将修改查询边权变为程序中的修改查询点权
void change(int x,int val){//更新权值
splay(x); v[x]=val; update(x);
}
void rever(int x,int y){//反转
access(x); int a=access(y); /*别*/splay(x);/*漏!下同*/
if(x==a){
int r=c[x][]; tg[r]^=; v[r]=-v[r]; sm[r]=-sm[r]; int t=mx[r]; mx[r]=-mn[r]; mn[r]=-t; update(x);
}else{
tg[x]^=; v[x]=-v[x]; sm[x]=-sm[x]; int t=mx[x]; mx[x]=-mn[x]; mn[x]=-t;
int r=c[a][]; tg[r]^=; v[r]=-v[r]; sm[r]=-sm[r]; t=mx[r]; mx[r]=-mn[r]; mn[r]=-t;
update(a);
}
}
int querysum(int x,int y){//求和
access(x); int a=access(y); splay(x);
if(x==a)return sm[c[x][]];else return sm[x]+sm[c[a][]];
}
int querymax(int x,int y){//求最大值
access(x); int a=access(y); splay(x);
if(x==a)return mx[c[x][]];else return max(mx[x],mx[c[a][]]);
}
int querymin(int x,int y){//求最小值
access(x); int a=access(y); splay(x);
if(x==a)return mn[c[x][]];else return min(mn[x],mn[c[a][]]);
} int num[N];
void dfs(int x){//建树,并将边权转为点权 ,本程序用边的终点点权表示这条边的边权
for(int i=g[x];i!=-;i=es[i].n)if(es[i].t!=fa[x]){
fa[es[i].t]=x; v[es[i].t]=sm[es[i].t]=mx[es[i].t]=mn[es[i].t]=es[i].w; dfs(es[i].t);
}
}
int main(){
//freopen("big.txt","r",stdin); freopen("big.out","w",stdout);
memset(num,,sizeof(num)); ess=; memset(g,-,sizeof(g)); memset(fa,,sizeof(fa));
scanf("%d",&n); inc(i,,n-){int a,b,c; scanf("%d%d%d",&a,&b,&c); pe(a+,b+,c); num[i]=b+;}
dfs();
memset(c,,sizeof(c)); memset(tg,,sizeof(tg)); mn[]=INF; mx[]=-INF; scanf("%d",&m); char s[];
inc(i,,m){
int a,b; scanf("%s%d%d",s,&a,&b);
if(s[]=='C')change(num[a],b);
if(s[]=='N')a++,b++,rever(a,b);
if(s[]=='S')a++,b++,printf("%d\n",querysum(a,b));
if(s[]=='A')a++,b++,printf("%d\n",querymax(a,b));
if(s[]=='I')a++,b++,printf("%d\n",querymin(a,b));
}
return ;
}
20151215
-----------------------------------------------------------------------------------------------------
题解:
用树链剖分重写一下本题。树链剖分主要分3个步骤:
1、第一次dfs,求出各节点的子树大小、权值、深度等信息。
2、第二次dfs,求出各节点所在树链的头,以及各节点的中儿子、在所有树链依次相连组成的大链中的位置
3、将所有树链依次相连组成的大链建成一棵全局线段树
如何求lca(u,v)?
不停循环这样一个过程:u和v哪个深度大,就让它向上爬一条重链再爬一条轻边,直到两个所在链头节点相等(即在同一条树链)。此时,深度小的那个就是lca。
如何维护(查询)(u,v)之间信息?
求lca(u,v),然后对u不停循环这个过程:维护u到所在树链头的信息,接着u爬一条重链再一条轻边,直到树链头深度小于lca(此时u与lca在同一条树链),然后维护u到lca的信息。对v重复相同操作。
程序中我之所以存了每个节点的重儿子,是因为题意求边权,根据我的程序中边权与点权的转换方式,lca的权值不能被维护(查询),所以“维护u到lca的信息”就变成“维护u到lca重儿子的信息(如果此时u=lca,不能执行此操作)”
吐槽:线段树真耗空间,开的数组大小必须是点数的4倍!因为这个我re了5发。同时线段树的pushdown是边查询(修改)边执行的,需要注意。
对比链剖和link-cut tree,发现lct完胜!?不管在代码长度还是时间空间复杂度上都是lct更优。不是说线段树的常数比splay小得多吗?个人认为链剖输在一下几个方面:
1、链剖编程复杂度高,线段树要写一堆操作,链剖本身还要写一堆操作,同时链剖的操作还要写两个循环,大大增大了代码长度。
2、链剖空间复杂度高,4倍点数。这也是引起速度慢的一个重要原因,开大数组在一定程度上会增大程序的耗时。
总结:本题数据不是很大,链剖的适用范围应该是数据比较大的题,因为此时对程序本身常数的要求要高过空间引起的常数。
根本原因:我太弱了,肯定是我链剖写残才会这么慢!
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 20010
#define inc(i,j,k) for(int i=j;i<=k;i++)
#define INF 0x3fffffff
using namespace std; //gerneral
int n,m; //edge
struct e{int t,w,n;}; e es[N*]; int ess,g[N];
inline void pe(int f,int t,int w){es[++ess]=(e){t,w,g[f]}; g[f]=ess; es[++ess]=(e){f,w,g[t]}; g[t]=ess;} //segment tree
int sm[N*],mx[N*],mn[N*]/*节点信息*/,v[N][]/*中间数组*/,l[N*],r[N*]/*区间*/;
int lc[N*],rc[N*]/*左右儿子*/; bool tg[N*]/*标记*/;
void update(int x){
if(x==)return;
sm[x]=sm[lc[x]]+sm[rc[x]]; mx[x]=max(mx[lc[x]],mx[rc[x]]); mn[x]=min(mn[lc[x]],mn[rc[x]]);
}
void pushdown(int x){//标记意义同lct
if(x==||!tg[x])return; int L=lc[x],R=rc[x]; tg[x]^=;
if(L){tg[L]^=; sm[L]=-sm[L]; int t=mx[L]; mx[L]=-mn[L]; mn[L]=-t;}
if(R){tg[R]^=; sm[R]=-sm[R]; int t=mx[R]; mx[R]=-mn[R]; mn[R]=-t;}
}
void build(int x,int L,int R){//建线段树
l[x]=L; r[x]=R;
if(L==R)sm[x]=mx[x]=mn[x]=v[L][],lc[x]=rc[x]=tg[x]=;else{
int M=(L+R)>>; lc[x]=x<<; rc[x]=x<<|; build(lc[x],L,M); build(rc[x],M+,R); tg[x]=; update(x);
}
}
void change(int x,int nod,int val){//线段树点修改
pushdown(x);
if(l[x]==r[x])sm[x]=mx[x]=mn[x]=val;else{
int M=(l[x]+r[x])>>; nod<=M?change(lc[x],nod,val):change(rc[x],nod,val); update(x);
}
}
void rever(int x,int ql,int qr){//线段树区间修改
pushdown(x);
int M=(l[x]+r[x])>>; if(ql<=l[x]&&r[x]<=qr){tg[x]^=; sm[x]=-sm[x]; int t=mx[x]; mx[x]=-mn[x]; mn[x]=-t; return;}
if(ql<=M)rever(lc[x],ql,qr); if(qr>M)rever(rc[x],ql,qr); update(x);
}
int querysum(int x,int ql,int qr){//线段树求和
pushdown(x);
int ret=,M=(l[x]+r[x])>>; if(ql<=l[x]&&r[x]<=qr)return sm[x];
if(ql<=M)ret+=querysum(lc[x],ql,qr); if(qr>M)ret+=querysum(rc[x],ql,qr);
return ret;
}
int querymax(int x,int ql,int qr){//线段树求最大值
pushdown(x);
int ret=-INF,M=(l[x]+r[x])>>; if(ql<=l[x]&&r[x]<=qr)return mx[x];
if(ql<=M)ret=max(ret,querymax(lc[x],ql,qr)); if(qr>M)ret=max(ret,querymax(rc[x],ql,qr));
return ret;
}
int querymin(int x,int ql,int qr){//线段树求最小值
pushdown(x);
int ret=INF,M=(l[x]+r[x])>>; if(ql<=l[x]&&r[x]<=qr)return mn[x];
if(ql<=M)ret=min(ret,querymin(lc[x],ql,qr)); if(qr>M)ret=min(ret,querymin(rc[x],ql,qr));
return ret;
} //tree chain apart
int pos[N]/*点在线段树中位置*/,top[N]/*树链头节点*/,fa[N],sz[N]/*子树大小*/,sgs,dep[N]/*深度*/,ps[N]/*重儿子*/;
void dfs(int x){//得到节点的父亲、子树大小、权值、深度
sz[x]=;
for(int i=g[x];i!=;i=es[i].n)if(es[i].t!=fa[x]){
fa[es[i].t]=x; dep[es[i].t]=dep[x]+; v[es[i].t][]=es[i].w; dfs(es[i].t); sz[x]+=sz[es[i].t];
}
}
void buildchain(int x,int tp/*当前节点所在树链头*/){//通过子树大小构造树链
pos[x]=++sgs; v[sgs][]=v[x][]; top[x]=tp; ps[x]=;
for(int i=g[x];i!=;i=es[i].n)if(es[i].t!=fa[x]){
if(sz[es[i].t]>sz[ps[x]])ps[x]=es[i].t;
}
if(ps[x]==)return;
buildchain(ps[x],tp);
for(int i=g[x];i!=;i=es[i].n)if(es[i].t!=fa[x]){
if(es[i].t!=ps[x])buildchain(es[i].t,es[i].t);
}
}
void init(){
memset(fa,,sizeof(fa)); dep[]=; dfs();
sgs=; mx[]=-INF; mn[]=INF; sz[]=; buildchain(,); build(,,sgs);
}
int lca(int x,int y){
for(;top[x]!=top[y];x=fa[top[x]]){if(dep[top[x]]<dep[top[y]])swap(x,y);}
return dep[x]<dep[y]?x:y;
}
void solvechange(int x,int val){change(,pos[x],val);}
void solverever(int x,int y){
if(x==y)return; int a=lca(x,y);
while(dep[top[x]]>dep[a])rever(,pos[top[x]],pos[x])/*注意参数顺序,下同*/,x=fa[top[x]];
if(a!=x)rever(,pos[ps[a]],pos[x]);
while(dep[top[y]]>dep[a])rever(,pos[top[y]],pos[y]),y=fa[top[y]];
if(a!=y)rever(,pos[ps[a]],pos[y]);
}
int solvesum(int x,int y){
if(x==y)return ; int a=lca(x,y),ans=;
while(dep[top[x]]>dep[a])ans+=querysum(,pos[top[x]],pos[x]),x=fa[top[x]];
if(a!=x)ans+=querysum(,pos[ps[a]],pos[x]);
while(dep[top[y]]>dep[a])ans+=querysum(,pos[top[y]],pos[y]),y=fa[top[y]];
if(a!=y)ans+=querysum(,pos[ps[a]],pos[y]);
return ans;
}
int solvemax(int x,int y){
if(x==y)return ; int a=lca(x,y),ans=-INF;
while(dep[top[x]]>dep[a])ans=max(ans,querymax(,pos[top[x]],pos[x])),x=fa[top[x]];
if(a!=x)ans=max(ans,querymax(,pos[ps[a]],pos[x]));
while(dep[top[y]]>dep[a])ans=max(ans,querymax(,pos[top[y]],pos[y])),y=fa[top[y]];
if(a!=y)ans=max(ans,querymax(,pos[ps[a]],pos[y]));
return ans;
}
int solvemin(int x,int y){
if(x==y)return ; int a=lca(x,y),ans=INF;
while(dep[top[x]]>dep[a])ans=min(ans,querymin(,pos[top[x]],pos[x])),x=fa[top[x]];
if(a!=x)ans=min(ans,querymin(,pos[ps[a]],pos[x]));
while(dep[top[y]]>dep[a])ans=min(ans,querymin(,pos[top[y]],pos[y])),y=fa[top[y]];
if(a!=y)ans=min(ans,querymin(,pos[ps[a]],pos[y]));
return ans;
} //main
int num[N];
int main(){
//freopen("zs.txt","r",stdin); freopen("zs.out","w",stdout);
scanf("%d",&n); memset(g,,sizeof(g)); ess=;
inc(i,,n-){int a,b,c; scanf("%d%d%d",&a,&b,&c); a++; b++; pe(a,b,c); num[i]=b;}
init(); scanf("%d",&m); char s[];
inc(i,,m){
int a,b; scanf("%s%d%d",s,&a,&b);
if(s[]=='C')solvechange(num[a],b);
if(s[]=='N')a++,b++,solverever(a,b);
if(s[]=='S')a++,b++,printf("%d\n",solvesum(a,b));
if(s[]=='A')a++,b++,printf("%d\n",solvemax(a,b));
if(s[]=='I')a++,b++,printf("%d\n",solvemin(a,b));
}
return ;
}
20151218
bzoj2157旅游的更多相关文章
- [bzoj2157]旅游 (lct)
这个应该也算裸的模板题吧..主要是边权的问题,对于每条边u->v,我们可以新建一个节点代替他,把边的信息弄到新的点上,就变成u->x->v了... 当然了这样的话要防止u和v这些没用 ...
- BZOJ2157旅游——树链剖分+线段树
题目描述 Ray 乐忠于旅游,这次他来到了T 城.T 城是一个水上城市,一共有 N 个景点,有些景点之间会用一座桥连接.为了方便游客到达每个景点但又为了节约成本,T 城的任意两个景点之间有且只有一条路 ...
- BZOJ2157 旅游 【树剖 或 LCT】
题目 Ray 乐忠于旅游,这次他来到了T 城.T 城是一个水上城市,一共有 N 个景点,有些景点之间会用一座桥连接.为了方便游客到达每个景点但又为了节约成本,T 城的任意两个景点之间有且只有一条路径. ...
- BZOJ2157: 旅游(LCT)
Description Ray 乐忠于旅游,这次他来到了T 城.T 城是一个水上城市,一共有 N 个景点,有些景点之间会用一座桥连接.为了方便游客到达每个景点但又为了节约成本,T 城的任意两个景点之间 ...
- [BZOJ2157]旅游(树链剖分/LCT)
树剖裸题,当然LCT也可以. 树剖: #include<cstdio> #include<algorithm> #define ls (x<<1) #define ...
- BZOJ2157: 旅游
传送门 先讲一个悲伤地故事 RunID User Problem Result Memory Time Language Code_Length Submit_Time 1635823 Cydiate ...
- BZOJ2157: 旅游 树链剖分 线段树
http://www.lydsy.com/JudgeOnline/problem.php?id=2157 在对树中数据进行改动的时候需要很多pushdown(具体操作见代码),不然会wa,大概原因 ...
- 【树链剖分】【线段树】bzoj2157 旅游
#include<cstdio> #include<algorithm> using namespace std; #define INF 2147483647 #define ...
- bzoj2157 旅游——LCT
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2157 仍然是LCT模板题~ 不过有一些需要注意的地方,点和边的区分,0号点的 mx 和 mn ...
随机推荐
- http的几种请求方法
1.HTTP请求方法有以下8种方法:OPTIONS.GET.HEAD.POST.PUT.DELETE.TRACE和CONNECT. GET GET请求会显示请求指定的资源.一般来说GET方法应该只用于 ...
- user is not in the sudoers file
使用用户账户使用sudo来运行一些特权命令时出现了如下错误(sudo是一个允许特定的用户组用另一个用户(典型的是root)的特权来运行一个命令): user is not in the sudoers ...
- Redis命令速查
目录 string list set zset hash 记录下常用的,方便查找 string 内部sds,动态扩容.小于1M加倍扩,大于1M每次扩容1M.最大长度512M. SET name val ...
- Newtonsoft 六个超简单又实用的特性,值得一试 【上篇】
一:讲故事 看完官方文档,阅读了一些 Newtonsoft 源码,对它有了新的认识,先总结 六个超经典又实用的特性,同大家一起分享,废话不多说,快来一起看看吧~~~ 二:特性分析 1. 代码格式化 如 ...
- <用户输入url按下回车,一直到用户看到界面,这期间经历了什么>
用户输入url按下回车,一直到用户看到界面,这期间都经历什么? 一. DNS解析缓存: 1. 找到浏览器缓存解析域名: 2. 找到和 DNS 缓存 ; 3. 找到路由器 DNS 缓存: 4. 找到查 ...
- 从Spring Initializr开始
出识springcloud我们这里需要建立两个项目 来感受下微服务 一.配置服务 1. Spring Initializr. 用idea自带的 Spring Initializr. 建立第一个项目 2 ...
- Elasticsearch修改分词器以及自定义分词器
Elasticsearch修改分词器以及自定义分词器 参考博客:https://blog.csdn.net/shuimofengyang/article/details/88973597
- F查询与Q查询、事务及其它
一.F查询和Q查询 1.1 F查询 在上面所有的例子中,我们构造的过滤器都是将字段值与某个我们自己设定的常量做比较.如果是对两个字段的值做比较,那这时候就要用到F查询了. Django提供F()来做这 ...
- MongoDB副本集replica set(三)--添加删除成员
在上一篇文章中,我们搭建了3个节点的副本集,集群信息如下: rstest:PRIMARY> rs.config() { "_id" : "rstest", ...
- 你想了解的 HTTPS 都在这里
HTTP 协议仅仅制定了互联网传输的标准,简化了直接使用 TCP 协议进行通信的难度.有关 HTTP 协议相关的讲解请看前面两节: HTTP 协议详解 HTTP协议详解(二) less is more ...