LCT总结
Posted jrf123
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LCT总结相关的知识,希望对你有一定的参考价值。
LCT:link-cut-tree
先粘个板子(可能理解深刻?)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int read() { int aa=0,bb=1;char cc=getchar(); while(cc>‘9‘||cc<‘0‘){if(cc==‘-‘) bb=-1;cc=getchar();} while(cc>=‘0‘&&cc<=‘9‘){aa=(aa<<3)+(aa<<1)+(cc^‘0‘);cc=getchar();} return aa*bb; } int n,m,w[100100],sum[100100],f[100100],ch[100100][2]; bool rev[100100]; bool isroot(int x) { return ch[f[x]][0]!=x&&ch[f[x]][1]!=x; } int get(int x) { return ch[f[x]][1]==x; } void update(int x) { sum[x]=w[x]^sum[ch[x][0]]^sum[ch[x][1]]; } void pushrev(int x) { rev[x]^=1; swap(ch[x][0],ch[x][1]); } void down(int x) { if(!rev[x]) return; rev[x]=0; pushrev(ch[x][0]); pushrev(ch[x][1]); } void rotate(int x) { int fa=f[x],gfa=f[fa],lr=get(x); ch[fa][lr]=ch[x][lr^1];f[ch[fa][lr]]=fa; if(!isroot(fa)) ch[gfa][get(fa)]=x;f[x]=gfa; ch[x][lr^1]=fa;f[ch[x][lr^1]]=x; update(fa);update(x); } void topushdown(int x) { if(!isroot(x)) topushdown(f[x]); down(x); } void splay(int x) { topushdown(x); for(;!isroot(x);rotate(x)) if(!isroot(f[x])) rotate(get(x)==get(f[x])?f[x]:x); } void access(int x) { for(int y=0;x;y=x,x=f[x]) splay(x),ch[x][1]=y,update(x); } void makeroot(int x) { access(x);splay(x);pushrev(x); } int findroot(int x) { access(x);splay(x); while(ch[x][0]) down(x),x=ch[x][0]; splay(x); return x; } void split(int x,int y) { makeroot(x);access(y);splay(y); } void link(int x,int y) { makeroot(x); if(findroot(y)!=x) f[x]=y; } void cut(int x,int y) { makeroot(x); if(findroot(y)!=x||f[y]!=x||ch[y][0]) return; f[y]=ch[x][1]=0; update(x); } int main() { n=read();m=read(); for(int i=1;i<=n;i++) w[i]=read(); int opt,x,y; for(int i=1;i<=m;i++){ opt=read();x=read();y=read(); if(opt==0) split(x,y),printf("%d ",sum[y]); if(opt==1) link(x,y); if(opt==2) cut(x,y); if(opt==3) makeroot(x),w[x]=y,update(x); } return 0; }
T1 Cave 洞穴勘测
辉辉热衷于洞穴勘测。某天,他按照地图来到了一片被标记为JSZX的洞穴群地区。经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连接了恰好两个洞穴。假如两个洞穴可以通过一条或者多条通道按一定顺序连接起来,那么这两个洞穴就是连通的,按顺序连接在一起的这些通道则被称之为这两个洞穴之间的一条路径。洞穴都十分坚固无法破坏,然而通道不太稳定,时常因为外界影响而发生改变,比如,根据有关仪器的监测结果,123号洞穴和127号洞穴之间有时会出现一条通道,有时这条通道又会因为某种稀奇古怪的原因被毁。辉辉有一台监测仪器可以实时将通道的每一次改变状况在辉辉手边的终端机上显示:如果监测到洞穴u和洞穴v之间出现了一条通道,终端机上会显示一条指令 Connect u v 如果监测到洞穴u和洞穴v之间的通道被毁,终端机上会显示一条指令 Destroy u v 经过长期的艰苦卓绝的手工推算,辉辉发现一个奇怪的现象:无论通道怎么改变,任意时刻任意两个洞穴之间至多只有一条路径。因而,辉辉坚信这是由于某种本质规律的支配导致的。因而,辉辉更加夜以继日地坚守在终端机之前,试图通过通道的改变情况来研究这条本质规律。然而,终于有一天,辉辉在堆积成山的演算纸中崩溃了……他把终端机往地面一砸(终端机也足够坚固无法破坏),转而求助于你,说道:“你老兄把这程序写写吧”。辉辉希望能随时通过终端机发出指令 Query u v,向监测仪询问此时洞穴u和洞穴v是否连通。现在你要为他编写程序回答每一次询问。已知在第一条指令显示之前,JSZX洞穴群中没有任何通道存在。
n≤10000, m≤200000
板子题,link和cut
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,m,f[10010],ch[10010][2]; bool rev[10010]; int get(int x) { return ch[f[x]][1]==x; } bool isroot(int x) { return ch[f[x]][0]!=x&&ch[f[x]][1]!=x; } void pushrev(int x) { rev[x]^=1; swap(ch[x][0],ch[x][1]); } void down(int x) { if(!rev[x]) return; rev[x]=0; pushrev(ch[x][0]); pushrev(ch[x][1]); } void topushdown(int x) { if(!isroot(x)) topushdown(f[x]); down(x); } void rotate(int x) { int fa=f[x],gfa=f[fa],lr=get(x); ch[fa][lr]=ch[x][lr^1];f[ch[fa][lr]]=fa; if(!isroot(fa)) ch[gfa][get(fa)]=x;f[x]=gfa; ch[x][lr^1]=fa;f[fa]=x; } void splay(int x) { topushdown(x); for(;!isroot(x);rotate(x)) if(!isroot(f[x])) rotate(get(x)==get(f[x])?f[x]:x); } void access(int x) { for(int y=0;x;y=x,x=f[x]) splay(x),ch[x][1]=y; } void makeroot(int x) { access(x);splay(x);pushrev(x); } int findroot(int x) { access(x);splay(x); while(ch[x][0]) down(x),x=ch[x][0]; splay(x); return x; } void link(int x,int y) { makeroot(x); if(findroot(y)!=x) f[x]=y; } void cut(int x,int y) { makeroot(x); if(findroot(y)!=x||f[y]!=x||ch[y][0]) return; f[y]=ch[x][1]=0; } int main() { scanf("%d%d",&n,&m); char c[10];int x,y; for(int i=1;i<=m;i++){ scanf("%s%d%d",c,&x,&y); if(c[0]==‘C‘) link(x,y); if(c[0]==‘D‘) cut(x,y); if(c[0]==‘Q‘){ if(findroot(x)==findroot(y)) puts("Yes"); else puts("No"); } } return 0; }
T2 树的维护
LCT只能维护点值,而这道题要求维护边权,所以有一个常用套路:对每条边新建一个点,向原端点连边,把边权转化为点权,注意LCT维护的时候只维护新建的点的信息(update的时候判断x>n即可)
CHANGE操作只需要把代表边的点搞成根,然后直接改即可,改完update一下
NEGATE操作就像rev一样打懒标记就行,down的时候一起down下去就好
但是取反后还怎么维护最大值,我们发现取相反数后,最大值变成了最小值,最小值变成了最大值,所以只需要再维护一个最小值,取相反数的时候swap一下最大值最小值,在分别取相反数即可
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define inf 10000000; using namespace std; int n,w[20010],ch[20010][2],f[20010],mx[20010],mn[20010]; bool rev[20010],lz[20010]; int read() { int aa=0,bb=1;char cc=getchar(); while(cc>‘9‘||cc<‘0‘){if(cc==‘-‘) bb=-1;cc=getchar();} while(cc>=‘0‘&&cc<=‘9‘){aa=(aa<<3)+(aa<<1)+(cc^‘0‘);cc=getchar();} return aa*bb; } bool isroot(int x) { return ch[f[x]][0]!=x&&ch[f[x]][1]!=x; } bool get(int x) { return ch[f[x]][1]==x; } void pushrev(int x) { rev[x]^=1; swap(ch[x][0],ch[x][1]); } void pushlz(int x) { lz[x]^=1; swap(mx[x],mn[x]);mx[x]=-mx[x];mn[x]=-mn[x];w[x]=-w[x]; } void down(int x) { if(rev[x]) rev[x]=0,pushrev(ch[x][0]),pushrev(ch[x][1]); if(lz[x]) lz[x]=0,pushlz(ch[x][0]),pushlz(ch[x][1]); } void topushdown(int x) { if(!isroot(x)) topushdown(f[x]); down(x); } void update(int x) { // if(lz[x]) lz[x]=0,pushlz(ch[x][0]),pushlz(ch[x][1]); mx[x]=max(mx[ch[x][0]],mx[ch[x][1]]),mn[x]=min(mn[ch[x][0]],mn[ch[x][1]]); if(x>n) mx[x]=max(mx[x],w[x]),mn[x]=min(mn[x],w[x]); } void rotate(int x) { int fa=f[x],gfa=f[fa],lr=get(x); ch[fa][lr]=ch[x][lr^1];f[ch[fa][lr]]=fa; if(!isroot(fa)) ch[gfa][get(fa)]=x;f[x]=gfa; ch[x][lr^1]=fa;f[fa]=x; update(fa);update(x); } void splay(int x) { topushdown(x); for(;!isroot(x);rotate(x)) if(!isroot(f[x])) rotate(get(x)==get(f[x])?f[x]:x); } void access(int x) { for(int y=0;x;y=x,x=f[x]) splay(x),ch[x][1]=y,update(x); } void makeroot(int x) { access(x);splay(x);pushrev(x); } void split(int x,int y) { makeroot(x);access(y);splay(y); } int findroot(int x) { access(x);splay(x); while(ch[x][0]) down(x),x=ch[x][0]; splay(x); return x; } void link(int x,int y) { makeroot(x); if(findroot(y)!=x) f[x]=y; } int main() { n=read();int x,y;memset(mx,-0x3f,sizeof(mx));memset(mn,0x3f,sizeof(mn)); for(int i=1;i<n;i++){ x=read();y=read();w[i+n]=read(); link(x,i+n);link(i+n,y); } char c[10]; while(1){ scanf("%s",c); if(c[0]==‘D‘) break; x=read();y=read(); if(c[0]==‘C‘) makeroot(x+n),w[x+n]=y,update(x+n); if(c[0]==‘N‘) split(x,y),pushlz(y); if(c[0]==‘Q‘) split(x,y),printf("%d ",mx[y]); } return 0; }
T3 tree
一棵n个点的树,每个点的初始权值为1。对于这棵树有q个操作,每个操作为以下四种操作之一: + u v c:将u到v的路径上的点的权值都加上自然数c; - u1 v1 u2 v2:将树中原有的边(u1,v1)删除,加入一条新边(u2,v2),保证操作完之后仍然是一棵树; * u v c:将u到v的路径上的点的权值都乘上自然数c; / u v:询问u到v的路径上的点的权值和,求出答案对于51061的余数。
1<=n,q<=1e5 0<=c<=1e4
我记得学线段树的时候就有这么一道用线段树维护的题
对于有加有乘的修改,我们选择先乘后加
如果先加后乘,就不可避免的会出现小数,有精度损失而且麻烦
如果先乘后加,我们可以用乘法分配律展开,每次乘的时候只需要同时给add标记乘上即可
然后这道题就没啥了,一定要注意传标记的时候只需考虑上传下来的标记,而不是自己的标记,自己的已经算过了,然后就是要开long long
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define mod 51061 #define ll long long using namespace std; ll n,m,w[100100],add[100100],mul[100100],sum[100100],sz[100100],f[100100],ch[100100][2]; bool rev[100100]; ll read() { ll aa=0,bb=1;char cc=getchar(); while(cc>‘9‘||cc<‘0‘){if(cc==‘-‘) bb=-1;cc=getchar();} while(cc>=‘0‘&&cc<=‘9‘){aa=(aa<<3)+(aa<<1)+(cc^‘0‘);cc=getchar();} return aa*bb; } bool isroot(ll x) { return ch[f[x]][0]!=x&&ch[f[x]][1]!=x; } ll get(ll x) { return ch[f[x]][1]==x; } void update(ll x) { sz[x]=1; sum[x]=(w[x]+sum[ch[x][0]]+sum[ch[x][1]])%mod; if(ch[x][0]) sz[x]+=sz[ch[x][0]]; if(ch[x][1]) sz[x]+=sz[ch[x][1]]; } void pushrev(ll x) { rev[x]^=1; swap(ch[x][0],ch[x][1]); } void pushlz(ll x,ll lz1,ll lz2) { mul[x]=mul[x]*lz1%mod;add[x]=(add[x]*lz1%mod+lz2)%mod; sum[x]=(sum[x]*lz1%mod+lz2*sz[x]%mod)%mod; w[x]=(w[x]*lz1%mod+lz2)%mod; } void down(ll x) { if(rev[x]) rev[x]=0,pushrev(ch[x][0]),pushrev(ch[x][1]); if(mul[x]!=1||add[x]) pushlz(ch[x][0],mul[x],add[x]),pushlz(ch[x][1],mul[x],add[x]),mul[x]=1,add[x]=0; } void topushdown(ll x) { if(!isroot(x)) topushdown(f[x]); down(x); } void rotate(ll x) { ll fa=f[x],gfa=f[fa],lr=get(x); ch[fa][lr]=ch[x][lr^1];f[ch[fa][lr]]=fa; if(!isroot(fa)) ch[gfa][get(fa)]=x;f[x]=gfa; ch[x][lr^1]=fa;f[fa]=x; update(fa);update(x); } void splay(ll x) { topushdown(x); for(;!isroot(x);rotate(x)) if(!isroot(f[x])) rotate(get(x)==get(f[x])?f[x]:x); } void access(ll x) { for(ll y=0;x;y=x,x=f[x]) splay(x),ch[x][1]=y,update(x); } void makeroot(ll x) { access(x);splay(x);pushrev(x); } void split(ll x,ll y) { makeroot(x);access(y);splay(y); } ll findroot(ll x) { access(x);splay(x); while(ch[x][0]) down(x),x=ch[x][0]; splay(x); return x; } void link(ll x,ll y) { makeroot(x); if(findroot(y)!=x) f[x]=y; } void cut(ll x,ll y) { makeroot(x); if(findroot(y)!=x||f[y]!=x||ch[y][0]) return; f[y]=ch[x][1]=0; update(x); } int main() { n=read();m=read();ll x,y,z,u,v; for(ll i=1;i<=n;i++) w[i]=1,add[i]=0,mul[i]=1; for(ll i=1;i<n;i++){ x=read();y=read(); link(x,y); } char c[2]; for(ll i=1;i<=m;i++){ scanf("%s",c); if(c[0]==‘+‘) x=read(),y=read(),z=read(),split(x,y),pushlz(y,1,z); if(c[0]==‘-‘) x=read(),y=read(),u=read(),v=read(),cut(x,y),link(u,v); if(c[0]==‘*‘) x=read(),y=read(),z=read(),split(x,y),pushlz(y,z,0); if(c[0]==‘/‘) x=read(),y=read(),split(x,y),printf("%lld ",sum[y]%mod); } return 0; }
T4 水管局长数据加强版
SC省MY市有着庞大的地下水管网络,嘟嘟是MY市的水管局长(就是管水管的啦),嘟嘟作为水管局长的工作就是:每天供水公司可能要将一定量的水从x处送往y处,嘟嘟需要为供水公司找到一条从A至B的水管的路径,接着通过信息化的控制中心通知路径上的水管进入准备送水状态,等到路径上每一条水管都准备好了,供水公司就可以开始送水了。嘟嘟一次只能处理一项送水任务,等到当前的送水任务完成了,才能处理下一项。
在处理每项送水任务之前,路径上的水管都要进行一系列的准备操作,如清洗、消毒等等。嘟嘟在控制中心一声令下,这些水管的准备操作同时开始,但由于各条管道的长度、内径不同,进行准备操作需要的时间可能不同。供水公司总是希望嘟嘟能找到这样一条送水路径,路径上的所有管道全都准备就绪所需要的时间尽量短。嘟嘟希望你能帮助他完成这样的一个选择路径的系统,以满足供水公司的要求。另外,由于MY市的水管年代久远,一些水管会不时出现故障导致不能使用,你的程序必须考虑到这一点。
不妨将MY市的水管网络看作一幅简单无向图(即没有自环或重边):水管是图中的边,水管的连接处为图中的结点。输入文件第一行为3个整数:N, M, Q分别表示管道连接处(结点)的数目、目前水管(无向边)的数目,以及你的程序需要处理的任务数目(包括寻找一条满足要求的路径和接受某条水管坏掉的事实)。
以下M行,每行3个整数x, y和t,描述一条对应的水管。x和y表示水管两端结点的编号,t表示准备送水所需要的时间。我们不妨为结点从1至N编号,这样所有的x和y都在范围[1, N]内。
以下Q行,每行描述一项任务。其中第一个整数为k:若k=1则后跟两个整数A和B,表示你需要为供水公司寻找一条满足要求的从A到B的水管路径;若k=2,则后跟两个整数x和y,表示直接连接x和y的水管宣布报废(保证合法,即在此之前直接连接x和y尚未报废的水管一定存在)。N ≤ 100000 M ≤ 1000000 Q ≤ 100000
题意:找一张图中A-B的最长路径最短
有删边操作自然想到LCT,但LCT只能维护树,而这道题是张图,但是我们有最小生成树啊
最短的最长路径一定在这张图的最小生成树里,所以维护最小生成树即可
删边后会破坏他的联通性,无法再构造出新图(除非你重建一张),我不会删边啊怎么办
不会删边但我会加边,然后我们就发现这道题好像只有删边而没有加边,所以我们可以把询问倒过来处理,这样删边操作就变成了加边操作
在新加入一条x到y权值为w的边时,直接连会出环,所以我们选择在环上删掉一条边保证他仍是一棵树,如果x到y路径上的最大值比w大,那么删去最大值对应的边,加入x到y的边;如果要小,那就什么都不做
所以这道题需要维护的是最大边权的编号,一定要记得copy一份用copy后的,不要像我一样调一下午还不知道错哪了
x到y的边的编号用map映射一下就好
#include<iostream> #include<cstdio> #include<map> #include<algorithm> using namespace std; struct node { int x,y,w,id; bool flag; }h[2000100],hh[2000100]; struct Query { int opt,x,y,ans; }q[2000100]; int n,m,Q,nn,fath[2000100],ch[2001000][2],f[2001000],mx[2001000]; bool rev[2001000]; map<pair<int,int>,int>mp; int read() { int aa=0,bb=1;char cc=getchar(); while(cc>‘9‘||cc<‘0‘){if(cc==‘-‘) bb=-1;cc=getchar();} while(cc>=‘0‘&&cc<=‘9‘){aa=(aa<<3)+(aa<<1)+(cc^‘0‘);cc=getchar();} return aa*bb; } bool cmp1(node a,node b){return a.w<b.w;} bool cmp2(node a,node b){return a.id<b.id;} int find(int x) { if(x!=fath[x]) fath[x]=find(fath[x]); return fath[x]; } bool get(int x) { return ch[f[x]][1]==x; } bool isroot(int x) { return ch[f[x]][0]!=x&&ch[f[x]][1]!=x; } void update(int x) { if(h[mx[ch[x][0]]].w>h[mx[ch[x][1]]].w) mx[x]=mx[ch[x][0]]; else mx[x]=mx[ch[x][1]]; if(x>n&&h[x].w>h[mx[x]].w) mx[x]=x; } void pushrev(int x) { rev[x]^=1; swap(ch[x][0],ch[x][1]); } void down(int x) { if(!rev[x]) return; rev[x]=0; pushrev(ch[x][0]); pushrev(ch[x][1]); } void topushdown(int x) { if(!isroot(x)) topushdown(f[x]); down(x); } void rotate(int x) { int fa=f[x],gfa=f[fa],lr=get(x); ch[fa][lr]=ch[x][lr^1];f[ch[fa][lr]]=fa; if(!isroot(fa)) ch[gfa][get(fa)]=x;f[x]=gfa; ch[x][lr^1]=fa;f[fa]=x; update(fa);update(x); } void splay(int x) { topushdown(x); for(;!isroot(x);rotate(x)) if(!isroot(f[x])) rotate(get(x)==get(f[x])?f[x]:x); } void access(int x) { for(int y=0;x;y=x,x=f[x]) splay(x),ch[x][1]=y,update(x); } void makeroot(int x) { access(x);splay(x);pushrev(x); } void split(int x,int y) { makeroot(x);access(y);splay(y); } int findroot(int x) { access(x);splay(x); while(ch[x][0]) down(x),x=ch[x][0]; splay(x); return x; } void link(int x,int y) { makeroot(x); if(findroot(y)!=x) f[x]=y; } void cut(int x,int y) { makeroot(x); if(findroot(y)!=x||f[y]!=x||ch[y][0]) return; f[y]=ch[x][1]=0; update(x); } int main() { n=read();m=read();Q=read(); for(int i=1;i<=m;i++){ h[i+n].x=read();h[i+n].y=read();h[i+n].w=read();h[i+n].id=i+n; mp[make_pair(h[i+n].x,h[i+n].y)]=i+n;mp[make_pair(h[i+n].y,h[i+n].x)]=i+n; } for(int i=1;i<=Q;i++){ q[i].opt=read();q[i].x=read();q[i].y=read(); if(q[i].opt==2){ int id=mp[make_pair(q[i].x,q[i].y)]; h[id].flag=1; } } for(int i=n+1;i<=n+m+1;i++) hh[i]=h[i]; sort(hh+n+1,hh+n+m+1,cmp1); int cnt=0; for(int i=1;i<=n;i++) fath[i]=i; for(int i=n+1;i<=n+m;i++){ if(hh[i].flag) continue; int fx=find(hh[i].x),fy=find(hh[i].y); if(fx!=fy){ fath[fx]=fy; link(hh[i].x,hh[i].id);link(hh[i].id,hh[i].y); cnt++; if(cnt==n-1) break; } } for(int i=Q;i>=1;i--){ if(q[i].opt==1) split(q[i].x,q[i].y),q[i].ans=h[mx[q[i].y]].w; if(q[i].opt==2){ int id1=mp[make_pair(q[i].x,q[i].y)],id2; split(q[i].x,q[i].y);id2=mx[q[i].y]; if(h[id2].w>h[id1].w){ cut(h[id2].x,id2);cut(id2,h[id2].y); link(h[id1].x,id1);link(id1,h[id1].y); } } } for(int i=1;i<=Q;i++) if(q[i].opt==1) printf("%d ",q[i].ans); return 0; }
T5 GERALD07加强版
N个点M条边的无向图,询问保留图中编号在[l,r]的边的时候图中的联通块个数。强制在线
1≤N、M、K≤200,000
联通块数=点数-边数,所以只要统计有几条树边即可
依然是图,我们要转化为树,生成树
把出现的时间当做边权,维护最大生成树,一条边取代另一条边的时候,一定是取代越早出现的越好
对于一个区间[l,r],如果区间中的一条边取代的边的编号比l小,代表将前边的一个断开,将[l,r]中的两个联通块连成了一个(也就是有用边+1),贡献加一;如果比l大,代表把这个区间本来就联通的两部分断开换了种链接方式,对答案没影响。取代的是l之前的边就是这个区间中有用的边,用点数n减去就得到联通块数
用主席树维护每一条边替换的谁,最后查询主席树即可
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; struct node { int x,y,w,id; }h[200100]; struct tree { int l,r,cnt; }t[4001000]; int n,m,Q,tpy,cnt,rt[200100],root[200100],ch[400100][2],f[400100],mn[400100]; bool rev[400100]; int read() { int aa=0,bb=1;char cc=getchar(); while(cc>‘9‘||cc<‘0‘){if(cc==‘-‘) bb=-1;cc=getchar();} while(cc>=‘0‘&&cc<=‘9‘){aa=(aa<<3)+(aa<<1)+(cc^‘0‘);cc=getchar();} return aa*bb; } bool get(int x) { return ch[f[x]][1]==x; } bool isroot(int x) { return ch[f[x]][0]!=x&&ch[f[x]][1]!=x; } void pushrev(int x) { rev[x]^=1; swap(ch[x][0],ch[x][1]); } void down(int x) { if(!rev[x]) return; rev[x]=0; pushrev(ch[x][0]); pushrev(ch[x][1]); } void topushdown(int x) { if(!isroot(x)) topushdown(f[x]); down(x); } void update(int x) { if(h[mn[ch[x][0]]].w<h[mn[ch[x][1]]].w) mn[x]=mn[ch[x][0]]; else mn[x]=mn[ch[x][1]]; if(x>n&&h[x-n].w<h[mn[x]].w) mn[x]=x-n; } void rotate(int x) { int fa=f[x],gfa=f[fa],lr=get(x); ch[fa][lr]=ch[x][lr^1];f[ch[fa][lr]]=fa; if(!isroot(fa)) ch[gfa][get(fa)]=x;f[x]=gfa; ch[x][lr^1]=fa;f[fa]=x; update(fa);update(x); } void splay(int x) { topushdown(x); for(;!isroot(x);rotate(x)) if(!isroot(f[x])) rotate(get(x)==get(f[x])?f[x]:x); } void access(int x) { for(int y=0;x;y=x,x=f[x]) splay(x),ch[x][1]=y,update(x); } void makeroot(int x) { access(x);splay(x);pushrev(x); } void split(int x,int y) { makeroot(x);access(y);splay(y); } int findroot(int x) { access(x);splay(x); while(ch[x][0]) down(x),x=ch[x][0]; splay(x); return x; } void link(int x,int y) { makeroot(x); if(findroot(y)!=x) f[x]=y; } void cut(int x,int y) { makeroot(x); if(findroot(y)!=x||f[y]!=x||ch[y][0]) return; f[y]=ch[x][1]=0; update(x); } void insert(int &x,int l,int r,int pos) { t[++cnt]=t[x];x=cnt;t[x].cnt++; if(l==r) return; int mid=l+r>>1; if(pos<=mid) insert(t[x].l,l,mid,pos); else insert(t[x].r,mid+1,r,pos); } int query(int x,int y,int l,int r,int ql,int qr) { if(ql<=l&&r<=qr) return t[y].cnt-t[x].cnt; int mid=l+r>>1,as=0; if(ql<=mid) as+=query(t[x].l,t[y].l,l,mid,ql,qr); if(qr>mid) as+=query(t[x].r,t[y].r,mid+1,r,ql,qr); return as; } int main() { n=read();m=read();Q=read();tpy=read(); h[0].w=0x7fffffff; for(int i=1;i<=m;i++) h[i].x=read(),h[i].y=read(),h[i].w=i; for(int i=1;i<=m;i++){ rt[i]=rt[i-1]; if(h[i].x==h[i].y) continue; int fx=findroot(h[i].x),fy=findroot(h[i].y); if(fx!=fy){ link(h[i].x,i+n);link(i+n,h[i].y); insert(rt[i],0,m,0); } else{ split(h[i].x,h[i].y); int id=mn[h[i].y]; cut(h[id].x,id+n);cut(id+n,h[id].y); link(h[i].x,i+n);link(i+n,h[i].y); insert(rt[i],0,m,id); } } if(tpy==0){ int l,r; for(int i=1;i<=Q;i++){ l=read();r=read(); printf("%d ",n-query(rt[l-1],rt[r],0,m,0,l-1)); } } if(tpy==1){ int l,r,las=0; for(int i=1;i<=Q;i++){ l=read()^las;r=read()^las; las=n-query(rt[l-1],rt[r],0,m,0,l-1); printf("%d ",las); } } return 0; }
T6 难存的情缘
一天机房的夜晚,无数人在MC里奋斗着。。。大家都知道矿产对于MC来说是多么的重要,但由于矿越挖越少,勇士们不得不跑到更远的地方挖矿,但这样路途上就会花费相当大的时间,导致挖矿效率低下。
cjj提议修一条铁路,大家一致同意。
大家都被CH分配了一些任务:
zjmfrank2012负责绘制出一个矿道地图,这个地图包括家(当然这也是一个矿,毕竟不把家掏空我们是不会走的),和无数个矿,所以大家应该可以想出这是一个无向无环图,也就是一棵树。
Digital_T和cstdio负责铺铁路。。所以这里没他们什么事,两位可以劳作去了。
这个时候song526210932和RMB突然发现有的矿道会刷怪,并且怪的数量会发生变化。作为采矿主力,他们想知道从一个矿到另一个矿的路上哪一段会最困难。。。(困难值用zjm的死亡次数表示)。
输入文件的第一行有一个整数N,代表矿的数量。矿的编号是1到N。
接下来N-1行每行有三个整数a,b,c,代表第i号矿和第j号矿之间有一条路,在初始时这条路的困难值为c。
接下来有若干行,每行是“CHANGE i ti”或者“QUERY a b”,前者代表把第i条路(路按所给顺序从1到M编号)的困难值修改为ti,后者代表查询a到b所经过的道路中的最大困难值。
输入数据以一行“DONE”结束。
1<=N<=10000,1<=c<=1000000,1<=操作次数<=100000
板子题,把边化点,然后单点修改,查询最大值(跟T2的一样一样的,连样例都一样)
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int n,w[20010],ch[20010][2],f[20010],mx[20010]; bool rev[20010]; int read() { int aa=0,bb=1;char cc=getchar(); while(cc>‘9‘||cc<‘0‘){if(cc==‘-‘) bb=-1;cc=getchar();} while(cc>=‘0‘&&cc<=‘9‘){aa=(aa<<3)+(aa<<1)+(cc^‘0‘);cc=getchar();} return aa*bb; } bool get(int x) { return ch[f[x]][1]==x; } bool isroot(int x) { return ch[f[x]][0]!=x&&ch[f[x]][1]!=x; } void update(int x) { mx[x]=max(mx[ch[x][0]],mx[ch[x][1]]); if(x>n) mx[x]=max(mx[x],w[x]); } void pushrev(int x) { rev[x]^=1; swap(ch[x][0],ch[x][1]); } void down(int x) { if(!rev[x]) return; rev[x]=0; pushrev(ch[x][0]);pushrev(ch[x][1]); } void topushdown(int x) { if(!isroot(x)) topushdown(f[x]); down(x); } void rotate(int x) { int fa=f[x],gfa=f[fa],lr=get(x); ch[fa][lr]=ch[x][lr^1];f[ch[fa][lr]]=fa; if(!isroot(fa)) ch[gfa][get(fa)]=x;f[x]=gfa; ch[x][lr^1]=fa;f[fa]=x; update(fa);update(x); } void splay(int x) { topushdown(x); for(;!isroot(x);rotate(x)) if(!isroot(f[x])) rotate(get(x)==get(f[x])?f[x]:x); } void access(int x) { for(int y=0;x;y=x,x=f[x]) splay(x),ch[x][1]=y,update(x); } void makeroot(int x) { access(x);splay(x);pushrev(x); } void split(int x,int y) { makeroot(x);access(y);splay(y); } int findroot(int x) { access(x);splay(x); while(ch[x][0]) down(x),x=ch[x][0]; splay(x); return x; } void link(int x,int y) { makeroot(x); if(findroot(y)!=x) f[x]=y; } int main() { n=read();int x,y; for(int i=1;i<n;i++){ x=read();y=read();w[i+n]=read(); link(x,i+n);link(i+n,y); } char c[10]; while(1){ scanf("%c",c); if(c[0]==‘D‘) break; x=read();y=read(); if(c[0]==‘C‘) makeroot(x+n),w[x+n]=y,update(x+n); if(c[0]==‘Q‘) split(x,y),printf("%d ",mx[y]); } return 0; }
T7 魔法森林
为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐士。
魔法森林可以被看成一个包含n个节点没m条边的无向图,节点标号为1~n,边标号为1~m。初始时小 E 同学在号节点1,隐士则住在n号节点。小 E 需要通过这一片魔法森林,才能够拜访到隐士。
魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。幸运的是,在1号节点住着两种守护精灵: A型守护精灵与B型守护精灵。小 E 可以借助它们的力量,达到自己的目的。
只要小 E 带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无向图中的每一条边$e_i$包含两个权值 $a_i$与$b_i$ 。若身上携带的 A 型守护精灵个数不少于$a_i$,且B型守护精灵个数不少于$b_i$,这条边上的妖怪就不会对通过这条边的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向小 E 发起攻击,他才能成功找到隐士。
由于携带守护精灵是一件非常麻烦的事,小 E 想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为a型守护精灵的个数与b型守护精灵的个数之和。
2<=n<=5e4 0<=m<=1e5 1<=$a_i$,$b_i$<=5e4
依然是图,依然要生成树。。。
要同时维护两个权值,不太好搞,考虑先让一个有序,在考虑另一个
所以先按a值排序,动态维护生成树
如果当前枚举的边x-y不在同一个联通块里,直接连接就好;如果在同一个联通块里,若x-y路径上最大的b比当前b要大,替换,否则不用理他
这样做是对的(一直感觉自己伪了,后来发现并没有。。。),因为已经按a排序,所以当前的a已经比在树中的a要大,如果要替换,就是a小b大和a大b小,原树中的a和b已经更新过答案,若当前b要小,可能会使答案更优,并且在以后,如果新加入一条边,那条边的a一定比他俩都大,所以他俩的a都没啥用,关键就是要最小的那个b,原树中的b大一定不会优,所以直接替换就好(我感觉我的思维很混乱。。。)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define inf 2147483647 using namespace std; struct node { int x,y,a,b; }h[200100]; int n,m,ans,mx1[200100],mx2[200100],ch[200100][2],f[200100]; bool rev[200100]; int read() { int aa=0,bb=1;char cc=getchar(); while(cc>‘9‘||cc<‘0‘){if(cc==‘-‘) bb=-1;cc=getchar();} while(cc>=‘0‘&&cc<=‘9‘){aa=(aa<<3)+(aa<<1)+(cc^‘0‘);cc=getchar();} return aa*bb; } bool cmp(node a,node b){return a.a<b.a;} bool isroot(int x) { return ch[f[x]][0]!=x&&ch[f[x]][1]!=x; } bool get(int x) { return ch[f[x]][1]==x; } void pushrev(int x) { rev[x]^=1; swap(ch[x][0],ch[x][1]); } void down(int x) { if(!rev[x]) return; rev[x]=0; pushrev(ch[x][0]); pushrev(ch[x][1]); } void topushdown(int x) { if(!isroot(x)) topushdown(f[x]); down(x); } void update(int x) { if(h[mx1[ch[x][0]]].a>h[mx1[ch[x][1]]].a) mx1[x]=mx1[ch[x][0]]; else mx1[x]=mx1[ch[x][1]]; if(x>n&&h[x-n].a>h[mx1[x]].a) mx1[x]=x-n; if(h[mx2[ch[x][0]]].b>h[mx2[ch[x][1]]].b) mx2[x]=mx2[ch[x][0]]; else mx2[x]=mx2[ch[x][1]]; if(x>n&&h[x-n].b>h[mx2[x]].b) mx2[x]=x-n; } void rotate(int x) { int fa=f[x],gfa=f[fa],lr=get(x); ch[fa][lr]=ch[x][lr^1];f[ch[fa][lr]]=fa; if(!isroot(fa)) ch[gfa][get(fa)]=x;f[x]=gfa; ch[x][lr^1]=fa;f[fa]=x; update(fa);update(x); } void splay(int x) { topushdown(x); for(;!isroot(x);rotate(x)) if(!isroot(f[x])) rotate(get(x)==get(f[x])?f[x]:x); } void access(int x) { for(int y=0;x;y=x,x=f[x]) splay(x),ch[x][1]=y,update(x); } void makeroot(int x) { access(x);splay(x);pushrev(x); } void split(int x,int y) { makeroot(x);access(y);splay(y); } int findroot(int x) { access(x);splay(x); while(ch[x][0]) down(x),x=ch[x][0]; splay(x); return x; } void link(int x,int y) { makeroot(x); if(findroot(y)!=x) f[x]=y; } void cut(int x,int y) { makeroot(x); if(findroot(y)!=x||f[y]!=x||ch[y][0]) return; f[y]=ch[x][1]=0; update(x); } int main() { n=read();m=read();ans=inf; for(int i=1;i<=m;i++) h[i].x=read(),h[i].y=read(),h[i].a=read(),h[i].b=read(); sort(h+1,h+m+1,cmp); for(int i=1;i<=m;i++){ int fx=findroot(h[i].x),fy=findroot(h[i].y); if(fx!=fy) link(h[i].x,i+n),link(i+n,h[i].y); else{ split(h[i].x,h[i].y);int id=mx2[h[i].y]; if(h[id].b>h[i].b){ cut(h[id].x,id+n);cut(id+n,h[id].y); link(h[i].x,i+n);link(i+n,h[i].y); } } if(findroot(1)==findroot(n)) split(1,n),ans=min(ans,h[mx1[n]].a+h[mx2[n]].b); } if(ans==inf) puts("-1"); else printf("%d ",ans); return 0; }
联赛后的第一篇blogs
以上是关于LCT总结的主要内容,如果未能解决你的问题,请参考以下文章