今日小结:今天本来想把重点放在平衡树和主席树上来着,结果上午做了一个树剖题,耗了一上午时间才调出来,然后下午去听课掌握了主席树的基本思路,晚上打了两个小时的比赛,做了几个题,计划有变更,明天先回归一下图论吧,做一下差分约束的题。
一. 完成的题目:
洛谷P1505,洛谷P4315,洛谷P3834,洛谷P4116,洛谷P4114
二.
1.当日完成题目数:5道。
2. 未完成6个题目的原因:下午去听了课,晚上打的牛客网比赛
3. 复习的知识点:树链剖分,线段树,主席树,平衡树
4.不会题目:洛谷P2146
三:
1. 洛谷P1505 [国家集训队]旅游
题目描述
\(Ray\) 乐忠于旅游,这次他来到了\(T\) 城。\(T\) 城是一个水上城市,一共有 \(N\) 个景点,有些景点之间会用一座桥连接。为了方便游客到达每个景点但又为了节约成本,\(T\) 城的任意两个景点之间有且只有一条路径。换句话说, \(T\) 城中只有\(N − 1\) 座桥。
\(Ray\) 发现,有些桥上可以看到美丽的景色,让人心情愉悦,但有些桥狭窄泥泞,令人烦躁。于是,他给每座桥定义一个愉悦度\(w\),也就是说,\(Ray\) 经过这座桥会增加\(w\) 的愉悦度,这或许是正的也可能是负的。有时,\(Ray\) 看待同一座桥的心情也会发生改变。
现在,\(Ray\) 想让你帮他计算从\(u\) 景点到\(v\) 景点能获得的总愉悦度。有时,他还想知道某段路上最美丽的桥所提供的最大愉悦度,或是某段路上最糟糕的一座桥提供的最低愉悦度。
输入输出格式
输入格式:
输入的第一行包含一个整数\(N\),表示\(T\) 城中的景点个数。景点编号为 \(0...N − 1\)。
接下来\(N − 1\) 行,每行三个整数\(u\)、\(v\) 和\(w\),表示有一条u 到\(v\),使 \(Ray\) 愉悦度增加\(w\) 的桥。桥的编号为\(1...N − 1\)。\(|w| \leq 1000\)。 输入的第\(N + 1\) 行包含一个整数\(M\),表示\(Ray\) 的操作数目。
接下来有\(M\) 行,每行描述了一个操作,操作有如下五种形式:
\(C\) \(i\) \(w\),表示\(Ray\) 对于经过第\(i\) 座桥的愉悦度变成了\(w\)。
\(N\) \(u\) \(v\),表示\(Ray\) 对于经过景点\(u\) 到\(v\) 的路径上的每一座桥的愉悦度都变成原来的相反数。
\(SUM\) \(u\) \(v\),表示询问从景点\(u\) 到\(v\) 所获得的总愉悦度。
\(MAX\) \(u\) \(v\),表示询问从景点\(u\) 到\(v\) 的路径上的所有桥中某一座桥所提供的最大愉悦度。
\(MIN\) \(u\) \(v\),表示询问从景点\(u\) 到\(v\) 的路径上的所有桥中某一座桥所提供的最小愉悦度。
测试数据保证,任意时刻,\(Ray\) 对于经过每一座桥的愉悦度的绝对值小于等于\(1000\)。
输出格式:
对于每一个询问(操作\(S\)、\(MAX\) 和\(MIN\)),输出答案。
输入输出样例
输入样例#1:
3
0 1 1
1 2 2
8
SUM 0 2
MAX 0 2
N 0 1
SUM 0 2
MIN 0 2
C 1 3
SUM 0 2
MAX 0 2
输出样例#1:
3
2
1
-1
5
3
说明
很容易的基础题哦>.<
思路:如果说这道题是点权的话,那么就是一道树链剖分的板子题,但是,这道题是边权,那怎么办呢?可以发现,每个儿子只有一个父亲,那么我们就可以用这个儿子的点权来代替它与它父亲之间的边权,然后用树链剖分+线段树维护最大值,最小值和区间和即可。
代码:
#include<cstdio>
#include<algorithm>
#include<cctype>
#define maxn 200007
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
const int inf=0x7fffffff;
int n,m,num,head[maxn],a[maxn],size[maxn],d[maxn],top[maxn];
int cnt,sum[maxn<<2],lazy[maxn<<2],maxx[maxn<<2],minn[maxn<<2];
int fa[maxn],id[maxn],zrj[maxn],son[maxn];
char s1[8];
inline int qread() {
char c=getchar();int num=0,f=1;
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) num=num*10+c-'0';
return num*f;
}
struct node {
int v,w,nxt;
}e[maxn<<1];
inline void ct(int u, int v, int w) {
e[++num].v=v;
e[num].w=w;
e[num].nxt=head[u];
head[u]=num;
}
void dfs1(int u) {
size[u]=1;
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].v;
if(v!=fa[u]) {
d[v]=d[u]+1;
fa[v]=u;
zrj[v]=e[i].w;
dfs1(v);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
}
void dfs2(int u, int t) {
id[u]=++cnt;
top[u]=t;
a[cnt]=zrj[u];
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].v;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
inline void pushup(int rt) {
sum[rt]=sum[ls]+sum[rs];
maxx[rt]=max(maxx[ls],maxx[rs]);
minn[rt]=min(minn[ls],minn[rs]);
}
inline void pushdown(int rt) {
if(lazy[rt]) {
sum[ls]=-sum[ls],lazy[ls]^=1;
sum[rs]=-sum[rs],lazy[rs]^=1;
int t1=maxx[ls],t2=maxx[rs],s1=minn[ls],s2=minn[rs];
maxx[ls]=-s1,maxx[rs]=-s2,minn[ls]=-t1,minn[rs]=-t2;
lazy[rt]=0;
}
}
void build(int rt, int l, int r) {
if(l==r) {
sum[rt]=maxx[rt]=minn[rt]=a[l];
return;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(rt);
}
void add(int rt, int l, int r, int L, int val) {
if(l==r) {
sum[rt]=maxx[rt]=minn[rt]=val;
return;
}
pushdown(rt);
int mid=(l+r)>>1;
if(L<=mid) add(ls,l,mid,L,val);
else add(rs,mid+1,r,L,val);
pushup(rt);
}
void modify(int rt, int l, int r, int L, int R) {
if(L>r||R<l) return;
if(L<=l&&r<=R) {
sum[rt]=-sum[rt],lazy[rt]^=1;
int t=maxx[rt],s=minn[rt];
maxx[rt]=-s,minn[rt]=-t;
return;
}
int mid=(l+r)>>1;
pushdown(rt);
modify(ls,l,mid,L,R),modify(rs,mid+1,r,L,R);
pushup(rt);
}
int csum(int rt, int l, int r, int L, int R) {
if(L>r||R<l) return 0;
if(L<=l&&r<=R) return sum[rt];
int mid=(l+r)>>1;
pushdown(rt);
return csum(ls,l,mid,L,R)+csum(rs,mid+1,r,L,R);
}
int cmax(int rt, int l, int r, int L, int R) {
if(L>r||R<l) return -inf;
if(L<=l&&r<=R) return maxx[rt];
int mid=(l+r)>>1,ans=-inf;
pushdown(rt);
if(L<=mid) ans=max(ans,cmax(ls,l,mid,L,R));
if(R>mid) ans=max(ans,cmax(rs,mid+1,r,L,R));
return ans;
}
int cmin(int rt, int l, int r, int L, int R) {
if(L>r||R<l) return inf;
if(L<=l&&r<=R) return minn[rt];
int mid=(l+r)>>1,ans=inf;
pushdown(rt);
if(L<=mid) ans=min(ans,cmin(ls,l,mid,L,R));
if(R>mid) ans=min(ans,cmin(rs,mid+1,r,L,R));
return ans;
}
void cal(int x, int y) {
int fx=top[x],fy=top[y];
while(fx!=fy) {
if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
modify(1,1,cnt,id[fx],id[x]);
x=fa[fx],fx=top[x];
}
if(id[x]>id[y]) swap(x,y);
modify(1,1,cnt,id[x]+1,id[y]);
}
int query_max(int x, int y) {
int fx=top[x],fy=top[y],ans=-inf;
while(fx!=fy) {
if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
ans=max(ans,cmax(1,1,cnt,id[fx],id[x]));
x=fa[fx],fx=top[x];
}
if(id[x]>id[y]) swap(x,y);
ans=max(ans,cmax(1,1,cnt,id[x]+1,id[y]));
return ans;
}
int query_min(int x, int y) {
int fx=top[x],fy=top[y],ans=inf;
while(fx!=fy) {
if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
ans=min(ans,cmin(1,1,cnt,id[fx],id[x]));
x=fa[fx],fx=top[x];
}
if(id[x]>id[y]) swap(x,y);
ans=min(ans,cmin(1,1,cnt,id[x]+1,id[y]));
return ans;
}
int query_sum(int x, int y) {
int fx=top[x],fy=top[y],ans=0;
while(fx!=fy) {
if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
ans+=csum(1,1,cnt,id[fx],id[x]);
x=fa[fx],fx=top[x];
}
if(id[x]>id[y]) swap(x,y);
ans+=csum(1,1,cnt,id[x]+1,id[y]);
return ans;
}
int main() {
n=qread();
for(int i=1,u,v,w;i<n;++i) {
u=qread()+1,v=qread()+1,w=qread();
ct(u,v,w);ct(v,u,w);
}
dfs1(1);dfs2(1,1);build(1,1,n);
m=qread();
for(int i=1,x,y;i<=m;++i) {
scanf("%s",s1);x=qread()+1,y=qread()+1;
if(s1[0]=='C') add(1,1,n,id[x],y-1);
if(s1[0]=='N') cal(x,y);
if(s1[0]=='S') printf("%d\n",query_sum(x,y));
if(s1[1]=='I') printf("%d\n",query_min(x,y));
if(s1[1]=='A') printf("%d\n",query_max(x,y));
}
return 0;
}
2. 洛谷 P4315 月下“毛景树”
题目描述
毛毛虫经过及时的变形,最终逃过的一劫,离开了菜妈的菜园。 毛毛虫经过千山万水,历尽千辛万苦,最后来到了小小的绍兴一中的校园里。
爬啊爬~爬啊爬毛毛虫爬到了一颗小小的“毛景树”下面,发现树上长着他最爱吃的毛毛果~ “毛景树”上有\(N\)个节点和\(N-1\)条树枝,但节点上是没有毛毛果的,毛毛果都是长在树枝上的。但是这棵“毛景树”有着神奇的魔力,他能改变树枝上毛毛果的个数:
\(Change\) \(k\) \(w\):将第\(k\)条树枝上毛毛果的个数改变为\(w\)个。
\(Cover\) \(u\) \(v\) \(w\):将节点\(u\)与节点\(v\)之间的树枝上毛毛果的个数都改变为\(w\)个。
\(Add\) \(u\) \(v\) \(w\):将节点\(u\)与节点\(v\)之间的树枝上毛毛果的个数都增加\(w\)个。 由于毛毛虫很贪,于是他会有如下询问:
\(Max\) \(u\) \(v\):询问节点\(u\)与节点\(v\)之间树枝上毛毛果个数最多有多少个。
输入输出格式
输入格式:
第一行一个正整数\(N\)。
接下来\(N-1\)行,每行三个正整数\(U_i\),\(V_i\)和\(W_i\),第\(i+1\)行描述第i条树枝。表示第\(i\)条树枝连接节点\(U_i\)和节点\(V_i\),树枝上有\(W_i\)个毛毛果。 接下来是操作和询问,以“\(Stop\)”结束。
输出格式:
对于毛毛虫的每个询问操作,输出一个答案。
输入输出样例
输入样例#1:
4
1 2 8
1 3 7
3 4 9
Max 2 4
Cover 2 4 5
Add 1 4 10
Change 1 16
Max 2 4
Stop
输出样例#1:
9
16
说明
\(1 \leq N \leq 100,000\),操作+询问数目不超过\(100000\)。
保证在任意时刻,所有树枝上毛毛果的个数都不会超过\(10^9\)个。
思路:这道题目,如果把边权改为点权的话,那么就是一个树链剖分+线段树的裸题了,就是两遍\(dfs\)找出重儿子,对点进行重新编号,然后树链剖分即可。
但是!!——这题是边权,那怎么办呢?我们发现,一个点最多只有一个父亲结点,那么我们就可以考虑把这个点与其父亲结点之间边的边权转化为这个点的点权!那,之后,就变成了我们一开始说的树链剖分裸题了呀!还有一个非常重要的细节就是树链剖分查询和修改路径的时候,父亲结点是不在路径上的!因为父亲结点的点权代表的是它与它的父亲之间的边权,因此,在查询和修改的时候,最后左端点为\(id[x]+1\)。
代码:
#include<cstdio>
#include<algorithm>
#include<cctype>
#define maxn 100007
#define ll long long
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
int n,head[maxn],d[maxn],size[maxn],son[maxn],a[maxn],tag[maxn<<2];
int p[maxn],id[maxn],top[maxn],num,cnt,lazy[maxn<<2],fa[maxn],maxx[maxn<<2];
char s[10];
inline int qread() {
char c=getchar();int num=0,f=1;
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) num=num*10+c-'0';
return num*f;
}
struct node {
int v,w,nxt;
}e[maxn<<1];
inline void ct(int u, int v, int w) {
e[++num].v=v;
e[num].w=w;
e[num].nxt=head[u];
head[u]=num;
}
inline void pushup(int rt) {
maxx[rt]=max(maxx[ls],maxx[rs]);
}
inline void pushdown(int rt) {
if(tag[rt]>=0) {
lazy[ls]=lazy[rs]=0;
maxx[ls]=maxx[rs]=tag[ls]=tag[rs]=tag[rt];
tag[rt]=-1;
}
if(lazy[rt]) {
lazy[ls]+=lazy[rt];
lazy[rs]+=lazy[rt];
maxx[ls]+=lazy[rt];
maxx[rs]+=lazy[rt];
lazy[rt]=0;
}
}
void build(int rt, int l, int r) {
tag[rt]=-1;
if(l==r) {
maxx[rt]=a[l];
return;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(rt);
}
void modify1(int rt, int l, int r, int L, int R, int val) {
if(L>r||R<l) return;
if(L<=l&&r<=R) {
lazy[rt]+=val;
maxx[rt]+=val;
return;
}
pushdown(rt);
int mid=(l+r)>>1;
if(L<=mid) modify1(ls,l,mid,L,R,val);
if(R>mid) modify1(rs,mid+1,r,L,R,val);
pushup(rt);
}
void modify2(int rt, int l, int r, int L, int R, int val) {
if(L>r||R<l) return;
if(L<=l&&r<=R) {
maxx[rt]=tag[rt]=val;
lazy[rt]=0;
return;
}
pushdown(rt);
int mid=(l+r)>>1;
modify2(ls,l,mid,L,R,val),modify2(rs,mid+1,r,L,R,val);
pushup(rt);
}
int cmax(int rt, int l, int r, int L, int R) {
if(L<=l&&r<=R) return maxx[rt];
int ans=0;
int mid=(l+r)>>1;
pushdown(rt);
if(L<=mid) ans=max(ans,cmax(ls,l,mid,L,R));
if(R>mid) ans=max(ans,cmax(rs,mid+1,r,L,R));
return ans;
}
void dfs1(int u, int f) {
size[u]=1;
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].v;
if(v!=f) {
d[v]=d[u]+1;
fa[v]=u;
p[v]=e[i].w;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
}
void dfs2(int u, int t) {
id[u]=++cnt;
top[u]=t;
a[cnt]=p[u];
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].v;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
void cal1(int x, int y, int val) {
int fx=top[x],fy=top[y];
while(fx!=fy) {
if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
modify1(1,1,n,id[fx],id[x],val);
x=fa[fx],fx=top[x];
}
if(id[x]>id[y]) swap(x,y);
modify1(1,1,n,id[x]+1,id[y],val);
}
void cal2(int x, int y, int val) {
int fx=top[x],fy=top[y];
while(fx!=fy) {
if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
modify2(1,1,n,id[fx],id[x],val);
x=fa[fx],fx=top[x];
}
if(id[x]>id[y]) swap(x,y);
modify2(1,1,n,id[x]+1,id[y],val);
}
int query(int x, int y) {
int ans=0,fx=top[x],fy=top[y];
while(fx!=fy) {
if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
ans=max(ans,cmax(1,1,n,id[fx],id[x]));
x=fa[fx],fx=top[x];
}
if(id[x]>id[y]) swap(x,y);
ans=max(ans,cmax(1,1,n,id[x]+1,id[y]));
return ans;
}
int main() {
n=qread();
for(int i=1,u,v,w;i<n;++i) {
u=qread(),v=qread(),w=qread();
ct(u,v,w);ct(v,u,w);
}
dfs1(1,0),dfs2(1,1);build(1,1,n);
while(1) {
scanf("%s",s);
if(s[0]=='S') break;
int x=qread(),y=qread();
if(s[1]=='h') {
x=d[e[x*2-1].v]<d[e[x<<1].v]?e[x<<1].v:e[x*2-1].v;
modify2(1,1,n,id[x],id[x],y);
}
if(s[1]=='o') {
int zrj=qread();
cal2(x,y,zrj);
}
if(s[1]=='d') {
int zrj=qread();
cal1(x,y,zrj);
}
if(s[1]=='a') printf("%d\n",query(x,y));
}
return 0;
}
3. 洛谷 P3834 【模板】可持久化线段树 1(主席树)
题目背景
这是个非常经典的主席树入门题——静态区间第K小
数据已经过加强,请使用主席树。同时请注意常数优化
题目描述
如题,给定N个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
输入输出格式
输入格式:
第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。
第二行包含N个整数,表示这个序列各项的数字。
接下来M行每行包含三个整数l, r, k , 表示查询区间[l, r]内的第k小值。
输出格式:
输出包含k行,每行1个整数,依次表示每一次查询的结果
输入输出样例
输入样例#1:
5 5
25957 6405 15770 26287 26465
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1
输出样例#1:
6405
15770
26287
25957
26287
说明
数据范围:
对于20%的数据满足:\(1 \leq N, M \leq 10\)
对于50%的数据满足:\(1 \leq N, M \leq 10^3\)
对于80%的数据满足:\(1 \leq N, M \leq 10^5\)
对于100%的数据满足:\(1 \leq N, M \leq 2\cdot 10^5\)
对于数列中的所有数\(a_i\),均满足\(-{10}^9 \leq a_i \leq {10}^9\)
样例数据说明:
N=5,数列长度为5,数列从第一项开始依次为[25957, 6405, 15770, 26287, 26465 ]
第一次查询为[2, 2]区间内的第一小值,即为6405
第二次查询为[3, 4]区间内的第一小值,即为15770
第三次查询为[4, 5]区间内的第一小值,即为26287
第四次查询为[1, 2]区间内的第二小值,即为25957
第五次查询为[4, 4]区间内的第一小值,即为26287
思路:主席树板子题,理解的还不是很好,有点迷,现在就只会打板子。
代码:
#include<cstdio>
#include<algorithm>
#include<cctype>
#define maxn 200007
using namespace std;
int n,m,rt[maxn],cnt,a[maxn],s[maxn],t;
inline int qread() {
char c=getchar();int num=0,f=1;
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) num=num*10+c-'0';
return num*f;
}
struct node {
int lc,rc,ans;
}tree[maxn*20];
void add(int &now, int last, int l, int r, int x) {
now=++cnt;
tree[now].ans=tree[last].ans+1;
tree[now].lc=tree[last].lc,tree[now].rc=tree[last].rc;
if(l==r) return;
int mid=(l+r)>>1;
if(x<=mid) add(tree[now].lc,tree[last].lc,l,mid,x);
else add(tree[now].rc,tree[last].rc,mid+1,r,x);
}
int query(int x, int l, int r, int L, int R) {
if(l==r) return l;
int p=tree[tree[R].lc].ans-tree[tree[L].lc].ans;
int mid=(l+r)>>1;
if(x<=p) return query(x,l,mid,tree[L].lc,tree[R].lc);
else return query(x-p,mid+1,r,tree[L].rc,tree[R].rc);
}
int main() {
n=qread(),m=qread();
for(int i=1;i<=n;++i) s[i]=qread(),a[i]=s[i];
sort(s+1,s+1+n);
for(int i=1;i<=n;++i) {
int p=lower_bound(s+1,s+1+n,a[i])-s;
add(rt[i],rt[i-1],1,n,p);
}
for(int i=1,x,y,z;i<=m;++i) {
x=qread(),y=qread(),z=qread();
int p=query(z,1,n,rt[x-1],rt[y]);
printf("%d\n",s[p]);
}
return 0;
}
4. 洛谷 P4116 Qtree3
题目描述
给出\(N\)个点的一棵树(\(N-1\)条边),节点有白有黑,初始全为白
有两种操作:
\(0\) \(i\) : 改变某点的颜色(原来是黑的变白,原来是白的变黑)
\(1\) \(v\) : 询问\(1\)到\(v\)的路径上的第一个黑点,若无,输出\(-1\)
输入输出格式
输入格式:
第一行 \(N\),\(Q\),表示\(N\)个点和\(Q\)个操作
第二行到第\(N\)行\(N-1\)条无向边
再之后\(Q\)行,每行一个操作"\(0\) \(i\)" 或者"\(1\) \(v\)" \((1 ≤ i, v ≤ N)\).
输出格式:
对每个\(1\) \(v\)操作输出结果
输入输出样例
输入样例#1:
9 8
1 2
1 3
2 4
2 9
5 9
7 9
8 9
6 8
1 3
0 8
1 6
1 7
0 2
1 9
0 2
1 9
输出样例#1:
-1
8
-1
2
-1
说明
For \(1/3\) of the test cases, \(N=5000, Q=400000\).
For \(1/3\) of the test cases, \(N=10000, Q=300000\).
For \(1/3\) of the test cases, \(N=100000, Q=100000\).
思路:对于操作\(1\),显然我们可以利用线段树的单点修改操作来实现,对于操作\(2\),要求求\(1\)到\(v\)的路径上的第一个黑点,那么我们可以考虑维护两点之间路径之间是黑点的点的深度最浅值,可以用树链剖分+线段树来实现。
代码:
#include<cstdio>
#include<algorithm>
#include<cctype>
#define maxn 100007
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
const int inf=1e9+7;
int n,m,num,top[maxn],cnt,head[maxn],d[maxn],size[maxn],id[maxn];
int minn[maxn<<2],fa[maxn],son[maxn],a[maxn],w[maxn];
inline int qread() {
char c=getchar();int num=0,f=1;
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) num=num*10+c-'0';
return num*f;
}
struct node {
int v,nxt;
}e[maxn<<1];
inline void ct(int u, int v) {
e[++num].v=v;
e[num].nxt=head[u];
head[u]=num;
}
void dfs1(int u) {
size[u]=1;
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].v;
if(v!=fa[u]) {
d[v]=d[u]+1;
fa[v]=u;
dfs1(v);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
}
void dfs2(int u, int t) {
id[u]=++cnt;
top[u]=t;
a[cnt]=u;
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].v;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
inline void pushup(int rt) {
minn[rt]=min(minn[ls],minn[rs]);
}
void build(int rt, int l, int r) {
if(l==r) {
minn[rt]=inf;
return;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(rt);
}
void modify(int rt, int l, int r, int L) {
if(l==r) {
if(w[id[L]]^=1) minn[rt]=l;
else minn[rt]=inf;
return;
}
int mid=(l+r)>>1;
if(L<=mid) modify(ls,l,mid,L);
else modify(rs,mid+1,r,L);
pushup(rt);
}
int cmin(int rt, int l, int r, int L, int R) {
if(L>r||R<l) return inf;
if(L<=l&&r<=R) return minn[rt];
int mid=(l+r)>>1,ans=inf;
if(L<=mid) ans=min(ans,cmin(ls,l,mid,L,R));
if(R>mid) ans=min(ans,cmin(rs,mid+1,r,L,R));
return ans;
}
int query(int x, int y) {
int fx=top[x],fy=top[y],ans=inf;
while(fx!=fy) {
if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
ans=min(ans,cmin(1,1,cnt,id[fx],id[x]));
x=fa[fx],fx=top[x];
}
if(id[x]>id[y]) swap(x,y);
ans=min(ans,cmin(1,1,cnt,id[x],id[y]));
return ans;
}
int main() {
n=qread(),m=qread();
for(int i=1,u,v;i<n;++i) {
u=qread(),v=qread();
ct(u,v);ct(v,u);
}
dfs1(1);dfs2(1,1);build(1,1,n);
for(int i=1,k,x;i<=m;++i) {
k=qread(),x=qread();
if(!k) modify(1,1,n,id[x]);
else {
int zrj=query(1,x);
if(zrj==inf) printf("-1\n");
else printf("%d\n",a[zrj]);
}
}
return 0;
}
5. 洛谷 P4114 Qtree1
题目描述
给定一棵\(n\)个节点的树,有两个操作:
\(CHANGE\) \(i\) \(t_i\) 把第\(i\)条边的边权变成\(t_i\)
\(QUERY\) \(a\) \(b\) 输出从\(a\)到\(b\)的路径中最大的边权,当\(a=b\)的时候,输出\(0\)
输入输出格式
输入格式:
第一行输入一个\(n\),表示节点个数
第二行到第\(n\)行每行输入三个数,\(u_i\),\(v_i\),\(w_i\),分别表示 \(u_i\),\(v_i\)有一条边,边权是\(w_i\)
第\(n+1\)行开始,一共有不定数量行,每一行分别有以下三种可能
\(CHANGE\),\(QUERY\)同题意所述
\(DONE\)表示输入结束
输出格式:
对于每个\(QUERY\)操作,输出一个数,表示\(a\) \(b\)之间边权最大值
输入输出样例
输入样例#1:
3
1 2 1
2 3 2
QUERY 1 2
CHANGE 1 3
QUERY 1 2
DONE
输出样例#1:
1
3
说明
数据保证:
\(1 \leq n \leq 10^5\)
操作次数 \(\leq 3*10^5\)
\(wi\)和\(ti\) \(\leq 2^{31}−1\)
思路:读完题目之后……诶,这不是树链剖分裸题么??!!
然后……代码写到一半忽然发现是边权……而普通的树链剖分是点权,如果是点权的话,那这道题就是一个裸题了吧。然后,我们可以发现,一个点连向它父亲的边有且只有一个,即一个点只有一个父亲,那么我们就可以用它的点权来代替它与它父亲之间的那条边的边权,然后,就成了一个裸题了……
还有要注意的一点就是,区间查询的时候不要把LCA算上,因为它的点权代表它与它父亲的边权,不在查询的路径上。
代码:
#include<cstdio>
#include<algorithm>
#include<cctype>
#define maxn 100007
#define ll long long
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
int n,head[maxn],d[maxn],size[maxn],son[maxn],a[maxn],lazy[maxn<<2];
int p[maxn],id[maxn],top[maxn],num,cnt,fa[maxn],maxx[maxn<<2];
char s[10];
inline int qread() {
char c=getchar();int num=0,f=1;
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) num=num*10+c-'0';
return num*f;
}
struct node {
int v,w,nxt;
}e[maxn<<1];
inline void ct(int u, int v, int w) {
e[++num].v=v;
e[num].w=w;
e[num].nxt=head[u];
head[u]=num;
}
inline void pushup(int rt) {
maxx[rt]=max(maxx[ls],maxx[rs]);
}
inline void pushdown(int rt) {
if(lazy[rt]>=0) {
maxx[ls]=maxx[rs]=lazy[ls]=lazy[rs]=lazy[rt];
lazy[rt]=-1;
}
}
void build(int rt, int l, int r) {
lazy[rt]=-1;
if(l==r) {
maxx[rt]=a[l];
return;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(rt);
}
void modify(int rt, int l, int r, int L, int R, int val) {
if(L>r||R<l) return;
if(L<=l&&r<=R) {
maxx[rt]=lazy[rt]=val;
return;
}
pushdown(rt);
int mid=(l+r)>>1;
modify(ls,l,mid,L,R,val),modify(rs,mid+1,r,L,R,val);
pushup(rt);
}
int cmax(int rt, int l, int r, int L, int R) {
if(L<=l&&r<=R) return maxx[rt];
int ans=0;
int mid=(l+r)>>1;
pushdown(rt);
if(L<=mid) ans=max(ans,cmax(ls,l,mid,L,R));
if(R>mid) ans=max(ans,cmax(rs,mid+1,r,L,R));
return ans;
}
void dfs1(int u, int f) {
size[u]=1;
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].v;
if(v!=f) {
d[v]=d[u]+1;
fa[v]=u;
p[v]=e[i].w;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
}
void dfs2(int u, int t) {
id[u]=++cnt;
top[u]=t;
a[cnt]=p[u];
if(son[u]) dfs2(son[u],t);
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].v;
if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
}
int query(int x, int y) {
int ans=0,fx=top[x],fy=top[y];
while(fx!=fy) {
if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
ans=max(ans,cmax(1,1,n,id[fx],id[x]));
x=fa[fx],fx=top[x];
}
if(id[x]>id[y]) swap(x,y);
ans=max(ans,cmax(1,1,n,id[x]+1,id[y]));
return ans;
}
int main() {
n=qread();
for(int i=1,u,v,w;i<n;++i) {
u=qread(),v=qread(),w=qread();
ct(u,v,w);ct(v,u,w);
}
dfs1(1,0),dfs2(1,1);build(1,1,n);
while(1) {
scanf("%s",s);
if(s[0]=='D') break;
int x=qread(),y=qread();
if(s[0]=='Q') {
if(x==y) printf("0\n");
else printf("%d\n",query(x,y));
}
if(s[0]=='C') {
x=d[e[x*2-1].v]<d[e[x<<1].v]?e[x<<1].v:e[x*2-1].v;
modify(1,1,n,id[x],id[x],y);
}
}
return 0;
}