Link Cut Tree
前方高能
这里面的任何一道题目都有可能汲取完你半天的时间(平均)
模板:
//splay
struct node{
int ...,fa,son[2];
bool z;
}t[maxn];
bool isroot(int p){return t[t[p].fa].son[0]!=p&&t[t[p].fa].son[1]!=p;}
bool chk(int p){return t[t[p].fa].son[1]==p;}
void reverse(int p){if(p==0)return;swap(t[p].son[0],t[p].son[1]);t[p].z^=1;}
void pushup(int p){...}
void pushdown(int p){
if(t[p].z){
reverse(t[p].son[0]);
reverse(t[p].son[1]);
t[p].z=0;
}
}
void pushall(int p){if(!isroot(p))pushall(t[p].fa);pushdown(p);}
void rotate(int p){
int q=t[p].fa,r=t[q].fa,k=chk(p),s=t[p].son[!k];
t[q].son[k]=s,t[s].fa=q;
if(!isroot(q))t[r].son[chk(q)]=p;
t[p].fa=r;
t[p].son[!k]=q,t[q].fa=p;
pushup(q);
pushup(p);
}
void splay(int p){
pushall(p);
while(!isroot(p)){
int q=t[p].fa;
if(!isroot(q)){
if(chk(p)==chk(q))rotate(q);
else rotate(p);
}
rotate(p);
}
}
//lct
void access(int x){
for(int y=0;x;y=x,x=t[x].fa){
splay(x);
t[x].son[1]=y;
pushup(x);
}
}
void change(int x){access(x),splay(x);...}
int query(int x){access(x),splay(x);return ...;}
\(O(n\log n)\)
以下默认\(n,m\le 10^5\)
Part 1
A.
- 修改边权
- 询问路径上最大边权
解
首先把边权转化为点权。两种方法:
-
新加 \(n-1\) 个点代表边,点权为对应的边权;
-
把边权赋到深度较深的端点的点权上。
-
access+splay+修改+pushup
-
makeroot+splay+询问
B.
- 询问 \(a\) 到 \(b\) 的距离
- 询问 \(a\) 到 \(b\) 的单向路径上的第 \(k\) 个点
解
- access(b)+splay(b)+return t[ch[b][0]].sz
- access(b)+splay(b)+splay的kth操作
C.
每个节点有黑白两种颜色。
- 改变节点的颜色
- 询问 \(a\) 到 \(b\) 的单向路径上的第一个黑色的节点
解
- access+splay+修改
- 同splay求前驱操作
D.
有边权
- 改变节点颜色
- 求树上的最远点对
解
边权\(→\)点权
本题与上面3题不同,需要维护子树信息。
方法是,对于每一个节点开一个multiset,维护节点所有虚儿子的信息。
本题的难点在pushup函数。
维护四个值 \(mx,lmx,rmx,sum\) , \(mx\) 表示当前子树中最远点对距离, \(lmx\) 表示当前子树中深度最浅的点的最远点对距离, \(rmx\) 表示当前子树中深度最深的点的最远点对距离, \(sum\) 表示子树中点权和。
Code
#include<bits/stdc++.h>
#define maxn 100003
#define INF 1050000000
using namespace std;
struct edge{int to,next,w;}e[maxn<<1];
int head[maxn],cnte;
void add(int u,int v,int w){e[++cnte].to=v,e[cnte].w=w,e[cnte].next=head[u],head[u]=cnte;}
int n,val[maxn],a[maxn];
struct node{
int mx,lmx,rmx,sum,fa,son[2];
}t[maxn];
multiset<int> light_lmx[maxn],light_mx[maxn];
int first(const multiset<int>& st){return st.empty()?-INF:*--st.end();}
int second(const multiset<int>& st){return st.size()<=1?-INF:*----st.end();}
bool isroot(int p){return t[t[p].fa].son[0]!=p&&t[t[p].fa].son[1]!=p;}
bool chk(int p){return t[t[p].fa].son[1]==p;}
void pushup(int p){
int L=t[p].son[0],R=t[p].son[1],syk=max(val[p]?-INF:0,first(light_lmx[p])),
sykl=max(syk,t[L].rmx+a[p]),sykr=max(syk,t[R].lmx);
t[p].lmx=max(t[L].lmx,t[L].sum+a[p]+sykr);
t[p].rmx=max(t[R].rmx,t[R].sum+sykl);
t[p].mx=max(max(max(max(max(max(t[L].mx,t[R].mx),t[L].rmx+a[p]+sykr),t[R].lmx+sykl),first(light_mx[p])),
first(light_lmx[p])+second(light_lmx[p])),val[p]?-INF:max(first(light_lmx[p]),0));
t[p].sum=t[L].sum+t[R].sum+a[p];
}
void rotate(int p){
int q=t[p].fa,r=t[q].fa,k=chk(p),s=t[p].son[!k];
t[q].son[k]=s,t[s].fa=q;
if(!isroot(q))t[r].son[chk(q)]=p;
t[p].fa=r;
t[p].son[!k]=q,t[q].fa=p;
pushup(q);
pushup(p);
}
void splay(int p){
while(!isroot(p)){
int q=t[p].fa,r=t[q].fa;
if(!isroot(q)){
if(chk(p)==chk(q))rotate(q);
else rotate(p);
}
rotate(p);
}
}
void access(int x){
for(int y=0;x;y=x,x=t[x].fa){
splay(x);
if(t[x].son[1])light_lmx[x].insert(t[t[x].son[1]].lmx),light_mx[x].insert(t[t[x].son[1]].mx);
t[x].son[1]=y;
if(y)light_lmx[x].erase(light_lmx[x].find(t[y].lmx)),light_mx[x].erase(light_mx[x].find(t[y].mx));
pushup(x);
}
}
void change(int x){
access(x),splay(x);
val[x]^=1;
pushup(x);
}
int query(){
splay(1);
return t[1].mx;
}
void dfs(int u,int last){
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==last)continue;
t[v].fa=u;
a[v]=e[i].w;
dfs(v,u);
light_mx[u].insert(t[v].mx);
light_lmx[u].insert(t[v].lmx);
}
pushup(u);
}
void init(){for(int i=0;i<=n;i++)t[i].mx=t[i].lmx=t[i].rmx=-INF;}
char mo[2];
int main(){
scanf("%d",&n);
init();
for(int i=1;i<n;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
dfs(1,0);
int Q;
scanf("%d",&Q);
while(Q--){
scanf("%s",mo);
if(*mo=='C'){
int x;
scanf("%d",&x);
change(x);
}
else{
int tmp=query();
if(tmp<0)puts("They have disappeared.");
else printf("%d\n",tmp);
}
}
return 0;
}
E.
- 改变节点颜色
- 询问与节点 \(v\) 最近的节点
解
本题是D题的弱化版
对于每个节点维护三个值 \(sz,lmx,rmx\) , \(sz\) 指当前子树的大小,其他两个与上题意义相同。
Code
#include<bits/stdc++.h>
#define maxn 100003
#define INF 1050000000
using namespace std;
struct edge{int to,next;}e[maxn<<1];
int head[maxn],cnte;
void add(int u,int v){e[++cnte].to=v,e[cnte].next=head[u],head[u]=cnte;}
int n,val[maxn];
struct node{
int lmi,rmi,sz,fa,son[2];
}t[maxn];
multiset<int> light_lmi[maxn];
int first(const multiset<int>& st){return st.empty()?INF:*st.begin();}
bool isroot(int p){return t[t[p].fa].son[0]!=p&&t[t[p].fa].son[1]!=p;}
bool chk(int p){return t[t[p].fa].son[1]==p;}
void pushup(int p){
int L=t[p].son[0],R=t[p].son[1],syk=min(val[p]?INF:0,first(light_lmi[p])+1);
t[p].lmi=min(t[L].lmi,t[L].sz+min(syk,t[R].lmi+1));
t[p].rmi=min(t[R].rmi,t[R].sz+min(syk,t[L].rmi+1));
t[p].sz=t[L].sz+t[R].sz+1;
}
void rotate(int p){
int q=t[p].fa,r=t[q].fa,k=chk(p),s=t[p].son[!k];
t[q].son[k]=s,t[s].fa=q;
if(!isroot(q))t[r].son[chk(q)]=p;
t[p].fa=r;
t[p].son[!k]=q,t[q].fa=p;
pushup(q);
pushup(p);
}
void splay(int p){
while(!isroot(p)){
int q=t[p].fa,r=t[q].fa;
if(!isroot(q)){
if(chk(p)==chk(q))rotate(q);
else rotate(p);
}
rotate(p);
}
}
void access(int x){
for(int y=0;x;y=x,x=t[x].fa){
splay(x);
if(t[x].son[1])light_lmi[x].insert(t[t[x].son[1]].lmi);
t[x].son[1]=y;
if(y)light_lmi[x].erase(light_lmi[x].find(t[y].lmi));
pushup(x);
}
}
void change(int x){
access(x),splay(x);
val[x]^=1;
pushup(x);
}
int query(int x){
access(x),splay(x);
return t[x].rmi>=n?-1:t[x].rmi;
}
void dfs(int u,int last){
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==last)continue;
t[v].fa=u;
dfs(v,u);
light_lmi[u].insert(t[v].lmi);
}
pushup(u);
}
void init(){for(int i=0;i<=n;i++)t[i].lmi=t[i].rmi=INF,val[i]=1;}
int main(){
scanf("%d",&n);
init();
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs(1,0);
int Q;
scanf("%d",&Q);
while(Q--){
int mo,x;
scanf("%d%d",&mo,&x);
if(mo){
printf("%d\n",query(x));
}
else{
change(x);
}
}
return 0;
}
F.
- 改变节点颜色
- 把颜色相同且相邻的节点看成一个连通块,询问某节点所在连通块大小。
解 1
运用link和cut函数,每次更改节点后大力link、cut更改节点的邻边。
但是会被菊花图给卡掉
解 2
点→边
建黑、白两颗LCT,如果当前节点为白,则在白LCT中与它的父亲连边,否则在黑LCT中。
这样能保证时间复杂度正确。
注意判断一个连通块的根节点是否与该连通块颜色相同。
Code
#include<bits/stdc++.h>
#define maxn 100003
using namespace std;
template<typename tp>
void read(tp& x){
x=0;
char c=getchar();
bool sgn=0;
while((c<'0'||c>'9')&&c!='-')c=getchar();
if(c=='-')sgn=1,c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
if(sgn)x=-x;
}
template<typename tp>
void write(tp x){
if(x<0)putchar('-'),write(-x);
else{
if(x>=10)write(x/10);
putchar(x%10+'0');
}
}
struct edge{int to,next;}e[maxn<<1];
int head[maxn],cnte;
void add(int u,int v){e[++cnte].to=v,e[cnte].next=head[u],head[u]=cnte;}
int n,fa[maxn],val[maxn];
struct LCT{
struct node{
int sum,light,fa,son[2];
}t[maxn];
bool isroot(int p){return t[t[p].fa].son[0]!=p&&t[t[p].fa].son[1]!=p;}
bool chk(int p){return t[t[p].fa].son[1]==p;}
void pushup(int p){
t[p].sum=t[t[p].son[0]].sum+t[t[p].son[1]].sum+t[p].light+1;
}
void rotate(int p){
int q=t[p].fa,r=t[q].fa,k=chk(p),s=t[p].son[!k];
t[q].son[k]=s,t[s].fa=q;
if(!isroot(q))t[r].son[chk(q)]=p;
t[p].fa=r;
t[p].son[!k]=q,t[q].fa=p;
pushup(q);
pushup(p);
}
void splay(int p){
while(!isroot(p)){
int q=t[p].fa,r=t[q].fa;
if(!isroot(q)){
if(chk(p)==chk(q))rotate(q);
else rotate(p);
}
rotate(p);
}
}
int find(int p){
while(t[p].son[0])p=t[p].son[0];
splay(p);
return p;
}
void access(int x){
for(int y=0;x;y=x,x=t[x].fa){
splay(x);
t[x].light+=t[t[x].son[1]].sum;
t[x].son[1]=y;
t[x].light-=t[t[x].son[1]].sum;
pushup(x);
}
}
void link(int x){
if(fa[x]==0)return;
access(fa[x]),splay(fa[x]),splay(x);
t[x].fa=fa[x];
t[fa[x]].light+=t[x].sum;
pushup(fa[x]);
}
void cut(int x){
if(fa[x]==0)return;
access(x),splay(x);
t[x].son[0]=t[t[x].son[0]].fa=0;
pushup(x);
}
int query(int x){
access(x),splay(x);
int tmp=find(x);
return val[tmp]==val[x]?t[tmp].sum:t[t[tmp].son[1]].sum;
}
void init(int n){for(int i=1;i<=n;i++)t[i].sum=1,t[i].light=t[i].fa=t[i].son[0]=t[i].son[1]=0;}
}tree[2];
void dfs(int u,int last){
fa[u]=last;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==last)continue;
dfs(v,u);
}
}
int main(){
read(n);
tree[0].init(n);
tree[1].init(n);
for(int i=1;i<n;i++){
int u,v;
read(u),read(v);
add(u,v),add(v,u);
}
dfs(1,0);
for(int i=1;i<=n;i++)tree[0].link(i);
int Q;
read(Q);
while(Q--){
int mo,x;
read(mo),read(x);
if(mo){
tree[val[x]].cut(x);
val[x]^=1;
tree[val[x]].link(x);
}
else{
write(tree[val[x]].query(x)),putchar('\n');
}
}
return 0;
}
G.
有点权
- 询问连通块中的最大点权和
- 改变节点颜色
- 修改点权
解
综合 \(C,F\) 两题思想,在 \(F\) 题基础上多维护一个 \(mx\) 。
Code
#include<bits/stdc++.h>
#define maxn 100003
#define INF 1050000000
using namespace std;
template<typename tp>
void read(tp& x){
x=0;
char c=getchar();
bool sgn=0;
while((c<'0'||c>'9')&&c!='-')c=getchar();
if(c=='-')sgn=1,c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
if(sgn)x=-x;
}
template<typename tp>
void write(tp x){
if(x<0)putchar('-'),write(-x);
else{
if(x>=10)write(x/10);
putchar(x%10+'0');
}
}
struct edge{int to,next;}e[maxn<<1];
int head[maxn],cnte;
void add(int u,int v){e[++cnte].to=v,e[cnte].next=head[u],head[u]=cnte;}
int n,fa[maxn],val[maxn],a[maxn];
struct LCT{
struct node{
int mx,fa,son[2];
};node t[maxn];
multiset<int,greater<int> > light[maxn];
bool isroot(int p){return t[t[p].fa].son[0]!=p&&t[t[p].fa].son[1]!=p;}
bool chk(int p){return t[t[p].fa].son[1]==p;}
void pushup(int p){
t[p].mx=max(max(max(t[t[p].son[0]].mx,t[t[p].son[1]].mx),a[p]),light[p].empty()?-INF:*light[p].begin());
}
void rotate(int p){
int q=t[p].fa,r=t[q].fa,k=chk(p),s=t[p].son[!k];
t[q].son[k]=s,t[s].fa=q;
if(!isroot(q))t[r].son[chk(q)]=p;
t[p].fa=r;
t[p].son[!k]=q,t[q].fa=p;
pushup(q);
pushup(p);
}
void splay(int p){
while(!isroot(p)){
int q=t[p].fa,r=t[q].fa;
if(!isroot(q)){
if(chk(p)==chk(q))rotate(q);
else rotate(p);
}
rotate(p);
}
}
int find(int p){
while(t[p].son[0])p=t[p].son[0];
splay(p);
return p;
}
void access(int x){
for(int y=0;x;y=x,x=t[x].fa){
splay(x);
light[x].insert(t[t[x].son[1]].mx);
t[x].son[1]=y;
light[x].erase(light[x].lower_bound(t[t[x].son[1]].mx));
pushup(x);
}
}
void link(int x){
if(fa[x]==0)return;
access(fa[x]),splay(fa[x]),splay(x);
t[x].fa=fa[x];
light[fa[x]].insert(t[x].mx);
pushup(fa[x]);
}
void cut(int x){
if(fa[x]==0)return;
access(x),splay(x);
t[x].son[0]=t[t[x].son[0]].fa=0;
pushup(x);
}
int query(int x){
access(x),splay(x);
int tmp=find(x);
return val[tmp]==val[x]?t[tmp].mx:t[t[tmp].son[1]].mx;
}
void init(int n){for(int i=0;i<=n;i++)t[i].mx=-INF,t[i].fa=t[i].son[0]=t[i].son[1]=0;}
}tree[2];
void dfs(int u,int last){
fa[u]=last;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==last)continue;
dfs(v,u);
}
}
int main(){
read(n);
tree[0].init(n);
tree[1].init(n);
for(int i=0;i<=n;i++)a[i]=-INF;
for(int i=1;i<n;i++){
int u,v;
read(u),read(v);
add(u,v),add(v,u);
}
for(int i=1;i<=n;i++)read(val[i]);
for(int i=1;i<=n;i++)read(a[i]),tree[0].t[i].mx=tree[1].t[i].mx=a[i];
dfs(1,0);
for(int i=1;i<=n;i++)tree[val[i]].link(i);
int Q;
read(Q);
while(Q--){
int mo,x;
read(mo),read(x);
if(mo==1){
tree[val[x]].cut(x);
val[x]^=1;
tree[val[x]].link(x);
}
else if(mo==2){
int y;
read(y);
tree[0].access(x),tree[0].splay(x),tree[1].access(x),tree[1].splay(x);
a[x]=y;
tree[0].pushup(x),tree[1].pushup(x);
}
else{
write(tree[val[x]].query(x)),putchar('\n');
}
}
return 0;
}
Part 2
A
\(N\) 个点 \(M\) 条边的无向图,询问保留图中编号在 \([l,r]\) 的边的时候图中的联通块个数。
解
考虑以下结论:
一个无向图中连通块的个数,等于它的顶点数-它的所有连通分量的生成树的边数和。
考虑离线做法。先将 \(r\) 排序,同时用树状数组维护 \(1-i\) 的边中当前已经扫到了哪些边。
Code
#include<bits/stdc++.h>
#define maxn 400003
#define INF 1050000000
#define o(a) printf(#a": ");for(int j=1;j<=n+m;j++)printf("%d ",a);puts("");
using namespace std;
struct edge{int from,to;}e[maxn];
int n,m,f[maxn],ans[maxn];
int find(int x){return x!=f[x]?f[x]=find(f[x]):f[x];}
struct QQ{int l,r,num;bool operator <(const QQ& x)const{return r<x.r;}}q[maxn];
struct node{int mi,fa,son[2];bool z;}t[maxn];
bool isroot(int p){return t[t[p].fa].son[0]!=p&&t[t[p].fa].son[1]!=p;}
bool chk(int p){return t[t[p].fa].son[1]==p;}
void reverse(int p){if(p==0)return;swap(t[p].son[0],t[p].son[1]);t[p].z^=1;}
void pushup(int p){t[p].mi=min(min(t[t[p].son[0]].mi,t[t[p].son[1]].mi),p<=n?INF:p);}
void pushdown(int p){
if(t[p].z){
reverse(t[p].son[0]);
reverse(t[p].son[1]);
t[p].z=0;
}
}
void pushall(int p){if(!isroot(p))pushall(t[p].fa);pushdown(p);}
void rotate(int p){
int q=t[p].fa,r=t[q].fa,k=chk(p),s=t[p].son[!k];
t[q].son[k]=s;
if(s)t[s].fa=q;
if(!isroot(q))t[r].son[chk(q)]=p;
t[p].fa=r;
t[p].son[!k]=q,t[q].fa=p;
pushup(q),pushup(p);
}
void splay(int p){
pushall(p);
while(!isroot(p)){
int q=t[p].fa;
if(!isroot(q)){
if(chk(p)==chk(q))rotate(q);
else rotate(p);
}
rotate(p);
}
}
void access(int x){for(int y=0;x;y=x,x=t[x].fa)splay(x),t[x].son[1]=y,pushup(x);}
void makeroot(int x){access(x),splay(x),reverse(x);}
void link(int x,int y){
makeroot(x);
t[x].fa=y;
}
void cut(int x,int y){
makeroot(x),access(y),splay(y);
t[x].fa=t[y].son[0]=0;
pushup(y);
}
int query(int x,int y){makeroot(x),access(y),splay(y);return t[y].mi;}
int TREE[maxn];
void ADD(int pos,int k){while(pos<=n+m)TREE[pos]+=k,pos+=pos&-pos;}
int QUERY(int pos){int ret=0;while(pos)ret+=TREE[pos],pos-=pos&-pos;return ret;}
int main(){
int T;
scanf("%d",&T);
while(T--){
int Q;
scanf("%d%d%d",&n,&m,&Q);
for(int i=n+1;i<=n+m;i++)scanf("%d%d",&e[i].from,&e[i].to);
for(int i=1;i<=Q;i++)scanf("%d%d",&q[i].l,&q[i].r),q[i].l+=n,q[i].r+=n,q[i].num=i;
sort(q+1,q+Q+1);
for(int i=1;i<=n;i++)f[i]=i;
for(int i=0;i<=n+m;i++)t[i].mi=t[i].fa=t[i].son[0]=t[i].son[1]=t[i].z=TREE[i]=0;
for(int i=n+1;i<=n+m;i++)t[i].mi=i;
t[0].mi=INF;
for(int i=n+1,j=1;i<=n+m;i++){
int u=e[i].from,v=e[i].to,fu=find(u),fv=find(v);
// printf("u:%d i:%d v:%d\n",u,i,v);
if(u!=v){
if(fu==fv){
int p=query(u,v);
cut(e[p].from,p),cut(p,e[p].to);
// printf("# u:%d p:%d v:%d\n",e[p].from,p,e[p].to);
ADD(p,-1);
}
else{
f[fu]=fv;
}
link(u,i),link(i,v);
ADD(i,1);
}
for(;j<=Q&&q[j].r<=i;j++){
ans[q[j].num]=n-QUERY(q[j].r)+QUERY(q[j].l-1);
// printf("q[j].num:%d ans:%d\n",q[j].num,ans[q[j].num]);
}
// o(t[j].fa);
}
for(int i=1;i<=Q;i++)printf("%d\n",ans[i]);
}
return 0;
}
E
同A,但强制在线。
解
把树状数组升级为主席树即可。
Code
#include<bits/stdc++.h>
#define maxn 400003
#define INF 1050000000
#define o(a) printf(#a": ");for(int j=1;j<=n+m;j++)printf("%d ",a);puts("");
using namespace std;
namespace FASTIO{
static const int MAXN=10000000;
char gc(){
static char In[MAXN],*at=In,*en=In;
if(at==en)en=(at=In)+fread(In,1,MAXN,stdin);
return at==en?EOF:*at++;
}
template<class tp>
void read(tp& x){
x=0;
char c=gc();
bool sgn=0;
while((c<'0'||c>'9')&&c!='-'&&c!=EOF)c=gc();
if(c==EOF)return;
if(c=='-')sgn=1,c=gc();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=gc();
if(sgn)x=-x;
}
void read(char* str){
char c=gc();
while((c==' '||c=='\n'||c=='\r'||c=='\t')&&c!=EOF)c=gc();
if(c!=EOF)while(c!=' '&&c!='\n'&&c!='\r'&&c!='\t')*str++=c,c=gc();
*str=0;
}
char _In[MAXN],*_at=_In;
void pc(char c){
if(_at==_In+MAXN)fwrite(_at=_In,1,MAXN,stdout);
*_at++=c;
}
template<typename tp>
void write(tp x){
if(x<0)pc('-'),write(-x);
else{
if(x>=10)write(x/10);
pc(x%10+'0');
}
}
void flush(){fwrite(_In,1,_at-_In,stdout),_at=_In;}
}using FASTIO::gc;using FASTIO::pc;using FASTIO::read;using FASTIO::write;using FASTIO::flush;
struct edge{int from,to;}e[maxn];
int n,m,f[maxn];
int find(int x){return x!=f[x]?f[x]=find(f[x]):f[x];}
struct node{int mi,fa,son[2];bool z;}t[maxn];
bool isroot(int p){return t[t[p].fa].son[0]!=p&&t[t[p].fa].son[1]!=p;}
bool chk(int p){return t[t[p].fa].son[1]==p;}
void reverse(int p){if(p==0)return;swap(t[p].son[0],t[p].son[1]);t[p].z^=1;}
void pushup(int p){t[p].mi=min(min(t[t[p].son[0]].mi,t[t[p].son[1]].mi),p<=n?INF:p);}
void pushdown(int p){
if(t[p].z){
reverse(t[p].son[0]);
reverse(t[p].son[1]);
t[p].z=0;
}
}
void pushall(int p){if(!isroot(p))pushall(t[p].fa);pushdown(p);}
void rotate(int p){
int q=t[p].fa,r=t[q].fa,k=chk(p),s=t[p].son[!k];
t[q].son[k]=s;
if(s)t[s].fa=q;
if(!isroot(q))t[r].son[chk(q)]=p;
t[p].fa=r;
t[p].son[!k]=q,t[q].fa=p;
pushup(q),pushup(p);
}
void splay(int p){
pushall(p);
while(!isroot(p)){
int q=t[p].fa;
if(!isroot(q)){
if(chk(p)==chk(q))rotate(q);
else rotate(p);
}
rotate(p);
}
}
void access(int x){for(int y=0;x;y=x,x=t[x].fa)splay(x),t[x].son[1]=y,pushup(x);}
void makeroot(int x){access(x),splay(x),reverse(x);}
void link(int x,int y){
makeroot(x);
t[x].fa=y;
}
void cut(int x,int y){
makeroot(x),access(y),splay(y);
t[x].fa=t[y].son[0]=0;
pushup(y);
}
int query(int x,int y){makeroot(x),access(y),splay(y);return t[y].mi;}
struct NODE{int sum,son[2];}TREE[maxn*40];
int CNT,ROOT[maxn];
void ADD(int p,int& q,int l,int r,int pos,int k){
q=++CNT;
TREE[q].sum=TREE[p].sum+k;
if(l==r)return;
int mid=(l+r)>>1;
if(pos<=mid){
TREE[q].son[1]=TREE[p].son[1];
ADD(TREE[p].son[0],TREE[q].son[0],l,mid,pos,k);
}
else{
TREE[q].son[0]=TREE[p].son[0];
ADD(TREE[p].son[1],TREE[q].son[1],mid+1,r,pos,k);
}
}
int QUERY(int p,int l,int r,int seg_l,int seg_r){
if(seg_l<=l&&r<=seg_r)return TREE[p].sum;
int mid=(l+r)>>1,ret=0;
if(seg_l<=mid)ret+=QUERY(TREE[p].son[0],l,mid,seg_l,seg_r);
if(seg_r>mid)ret+=QUERY(TREE[p].son[1],mid+1,r,seg_l,seg_r);
return ret;
}
int main(){
int Q,ty;
read(n),read(m),read(Q),read(ty);
for(int i=n+1;i<=n+m;i++)read(e[i].from),read(e[i].to);
for(int i=1;i<=n;i++)f[i]=i;
for(int i=n+1;i<=n+m;i++)t[i].mi=i;
t[0].mi=INF;
for(int i=n+1,j=1;i<=n+m;i++){
int u=e[i].from,v=e[i].to,fu=find(u),fv=find(v);
// printf("u:%d i:%d v:%d\n",u,i,v);
ROOT[i]=ROOT[i-1];
if(u!=v){
if(fu==fv){
int p=query(u,v);
cut(e[p].from,p),cut(p,e[p].to);
// printf("# u:%d p:%d v:%d\n",e[p].from,p,e[p].to);
ADD(ROOT[i],ROOT[i],n+1,n+m,p,-1);
}
else{
f[fu]=fv;
}
link(u,i),link(i,v);
ADD(ROOT[i],ROOT[i],n+1,n+m,i,1);
}
// o(t[j].fa);
}
int last=0;
while(Q--){
int l,r;
read(l),read(r);
if(ty)l^=last,r^=last;
last=n-QUERY(ROOT[r+n],n+1,n+m,l+n,r+n);
write(last),pc('\n');
}
flush();
return 0;
}
B ZJOI2018 历史
给出一棵树,给定每一个点的access次数,计算轻重链切换次数的最大值,带修改。
解
10 pts
\(\text{dfs}\)
30 pts(不带修改)
考虑树形dp。
假设现在考虑到节点 \(u\) ,把 \(u\) 的每一棵子树中所有节点看成同一种颜色,每个 \(u\) 的子节点(记为 \(v\) )的答案为 \(sum[v]\) , \(a[u]\) 为 \(u\;access\) 的次数。
设 \(s=\sum sum[v]+a[u],t=\max\{sum[v]\}\) ,那么:
举例说明:
-
第一种情况
颜色 \(A×3,B×4,C×5\)
一组可行解是 \(CACBCACBCBABCACBCACBCBAB\)
答案是 \(11=12-1\) -
第二种情况
颜色 \(A×2,B×3,C×7\)
一组可行解是 \(CACBCACBCBCCCACBCACBCBCC\)
答案是 \(10=2×(12-7)\)
100 pts
考虑如何修改。
我们看到 \(sum[u]=2*(s-t)\) 的条件是 \(2*t>s+1\)
然后发现这样的子树 \(v\) 在 \(u\) 的所有子树中最多只有1个
于是考虑树剖或LCT,如果某个 \(v\) 使得 \(2*t>s+1\) ,则 \((u,v)\) 为实(重)边,否则为虚(轻)边。下面我们讨论LCT做法。
\(pushup\) 维护两个数组 \(sum,light\) , \(sum\) 意义如上, \(light\) 维护虚边信息。
把修改嵌在access过程里,用以上的东西判断是否需要切换虚实边,同时更新各个数组。修改时需要同时更新答案。
Code
#include<bits/stdc++.h>
#define maxn 400003
#define o(a) printf(#a": ");for(int i=0;i<=n;i++)printf("%d ",a[i]);puts("");
using namespace std;
struct edge{int to,next;}e[maxn<<1];
int head[maxn],cnte;
void add(int u,int v){e[++cnte].to=v,e[cnte].next=head[u],head[u]=cnte;}
int n,tp[maxn];
long long sum[maxn],a[maxn],light[maxn],ans;
struct node{int fa,son[2];}t[maxn];
bool isroot(int p){return t[t[p].fa].son[0]!=p&&t[t[p].fa].son[1]!=p;}
bool chk(int p){return t[t[p].fa].son[1]==p;}
void pushup(int p){sum[p]=sum[t[p].son[0]]+sum[t[p].son[1]]+light[p]+a[p];}
void rotate(int p){
int q=t[p].fa,r=t[q].fa,k=chk(p),s=t[p].son[!k];
t[q].son[k]=s,t[s].fa=q;
if(!isroot(q))t[r].son[chk(q)]=p;
t[p].fa=r;
t[p].son[!k]=q,t[q].fa=p;
pushup(q);
pushup(p);
}
void splay(int p){
while(!isroot(p)){
int q=t[p].fa;
if(!isroot(q)){
if(chk(p)==chk(q))rotate(q);
else rotate(p);
}
rotate(p);
}
}
void init(int u){
int mxi=u;
long long mx=a[u];
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==t[u].fa)continue;
t[v].fa=u;
init(v);
light[u]+=sum[v];
if(sum[v]>mx)mx=sum[v],mxi=v;
}
sum[u]=light[u]+a[u];
if(mx*2>=sum[u]+1){
ans+=2*(sum[u]-mx);
if(u==mxi)tp[u]=1;
else tp[u]=2,t[u].son[1]=mxi,light[u]-=sum[mxi];
}
else{
tp[u]=0,ans+=sum[u]-1;
}
}
void change(int x,long long k){
for(int y=0;x;y=x,x=t[x].fa){
splay(x);
int &L=t[x].son[0],&R=t[x].son[1];
long long s=sum[x]-sum[L];
if(tp[x]==0)ans-=(s-1);
if(tp[x]==1)ans-=2*(s-a[x]);
if(tp[x]==2)ans-=2*(s-sum[R]);
s+=k,sum[x]+=k;
if(y==0)a[x]+=k;
else light[x]+=k;
if(sum[y]*2>=s+1)light[x]+=sum[R],R=y,light[x]-=sum[R];
if(sum[R]*2>=s+1)tp[x]=2,ans+=2*(s-sum[R]);
else{
if(R)light[x]+=sum[R],R=0;
if(a[x]*2>=s+1)tp[x]=1,ans+=2*(s-a[x]);
else tp[x]=0,ans+=s-1,R=0;
}
}
}
int main(){
int Q;
scanf("%d%d",&n,&Q);
for(int i=1;i<=n;i++)scanf("%lld",a+i);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
init(1);
printf("%lld\n",ans);
while(Q--){
int x;
long long k;
scanf("%d%lld",&x,&k);
change(x,k);
printf("%lld\n",ans);
}
return 0;
}
C
有 \(N\) 个未知数 \(x[1..n]\) 和 \(N\) 个等式组成的同余方程组:
\(x[i]=k[i]*x[p[i]]+b[i]\% 10007\)
其中, \(k[i],b[i],x[i]\in [0,10007)∩Z\)
你要应付 \(Q\) 个事务,每个是两种情况之一:
一、询问当前 \(x[a]\) 的解
A a
无解输出 \(-1\)
\(x[a]\) 有多解输出 \(-2\)
否则输出 \(x[a]\)
二、修改一个等式
C a k[a] p[a] b[a]
解
我们发现这是一个基环树森林,且每个节点只有一条出边。(连边 \(u→P[u]\) )
用LCT维护基环树的一般方法是对根节点记一个special_fa,每个节点维护一个关于 \(P[rt]\) 的一次函数。
于是在连边、断边时大力分类讨论。
对于询问,你通过一堆access+splay,能够得到两个方程,(设 \(u\) 为询问的节点, \(rt\) 为 \(u\) 所在树的根)形如 \(u=k_1*P[rt]+b_1\) 、 \(P[rt]=k_2*P[rt]+b_2\) 。
- \(k_2=1\;and\;b_2!=0\) ,无解
- \(k_2=1\;and\;b_2=0\) ,无穷解
- \(k_2!=1\) ,用exgcd求解(注意特判 \(k_2=0\) 的情况)
Code
#include<bits/stdc++.h>
#define maxn 30003
#define mod 10007
#define LINE puts("----------------------------------------------------------")
#define O(a) printf(#a": ");for(int i=1;i<=n;i++)printf("%d ",a);puts("");
using namespace std;
int n,Q,inv[maxn];
bool vis[maxn],instk[maxn];
int Plus(int x,int y){return (x+=y)>=mod?x-mod:x;}
int mul(int x,int y){return x*y%mod;}
int Div(int x,int y){return x*inv[y]%mod;}
int egcd(int a,int b,int& x,int& y){
if(b==0){
x=1;
y=0;
return a;
}
int ans=egcd(b,a%b,x,y);
int tmp=x;
x=y;
y=tmp-a/b*y;
return ans;
}
int cal(int a,int b,int c){ //ax+by=c (gcd(a,b)==1)
int x,y;
egcd(a,b,x,y);
x*=c;
int ans=x%b;
if(ans<=0)ans+=b;
return ans;
}
struct T{int K,B;T():K(1),B(0){}}a[maxn];
T operator +(const T& t2,const T& t3){T t1;t1.K=mul(t3.K,t2.K),t1.B=Plus(mul(t3.K,t2.B),t3.B);return t1;}
struct node{T val;int sfa,fa,son[2];}t[maxn];
bool isroot(int p){return t[t[p].fa].son[0]!=p&&t[t[p].fa].son[1]!=p;}
bool chk(int p){return t[t[p].fa].son[1]==p;}
void pushup(int p){
int L=t[p].son[0],R=t[p].son[1];
t[p].val=a[p];
if(L)t[p].val=t[L].val+t[p].val;
if(R)t[p].val=t[p].val+t[R].val;
}
void rotate(int p){
int q=t[p].fa,r=t[q].fa,k=chk(p),s=t[p].son[!k];
t[q].son[k]=s;
if(s)t[s].fa=q;
if(!isroot(q))t[r].son[chk(q)]=p;
t[p].fa=r;
t[p].son[!k]=q,t[q].fa=p;
pushup(q),pushup(p);
}
void splay(int p){
while(!isroot(p)){
int q=t[p].fa;
if(!isroot(q)){
if(chk(p)==chk(q))rotate(q);
else rotate(p);
}
rotate(p);
}
}
void access(int x){for(int y=0;x;y=x,x=t[x].fa)splay(x),t[x].son[1]=y,pushup(x);}
int findroot(int x){access(x),splay(x);while(t[x].son[0])x=t[x].son[0];return x;}
bool oncyc(int x){
int rt=t[findroot(x)].sfa;
if(x==rt)return 1;
access(rt),splay(rt),splay(x);
return !isroot(rt);
}
void link(int x,int y){ //dep[x]>dep[y]
access(x),splay(x);
t[x].fa=y;
}
void cut(int x){ //dep[x]>dep[y]
access(x),splay(x);
t[x].son[0]=t[t[x].son[0]].fa=0;
pushup(x);
}
void change(int i,int k,int p,int b){
if(findroot(i)==i){
t[i].sfa=0;
}
else{
bool flag=oncyc(i);
int rt=findroot(i);
cut(i);
if(flag){
splay(rt);
t[rt].fa=t[rt].sfa;
t[rt].sfa=0;
pushup(t[rt].fa);
}
}
a[i].K=k,a[i].B=b;
pushup(i);
if(findroot(i)==findroot(p)){
t[i].sfa=p;
}
else{
link(i,p);
}
}
int query(int i){
access(i),splay(i);
T I=t[i].val;
int p=t[findroot(i)].sfa;
access(p),splay(p);
T P=t[p].val;
if(P.K==1)return P.B?-1:-2;
if(P.K==0)return Plus(mul(I.K,P.B),I.B);
return Plus(mul(I.K,mod+cal(P.K-1,mod,-P.B)),I.B);
}
void dfs(int u){
vis[u]=instk[u]=1;
if(vis[t[u].fa]){
if(instk[t[u].fa]){
t[u].sfa=t[u].fa;
t[u].fa=0;
}
}
else{
dfs(t[u].fa);
}
instk[u]=0;
}
int main(){
inv[1]=1;
for(int i=2;i<mod;i++)inv[i]=mul(mod-mod/i,inv[mod%i]);
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&a[i].K,&t[i].fa,&a[i].B);
a[i].K%=mod,a[i].B%=mod;
t[i].val=a[i];
}
for(int i=1;i<=n;i++){
if(!vis[i])dfs(i);
}
scanf("%d",&Q);
while(Q--){
char mo[2];
scanf("%s",mo);
if(*mo=='A'){
int i;
scanf("%d",&i);
printf("%d\n",query(i));
}
else{
int i,k,p,b;
scanf("%d%d%d%d",&i,&k,&p,&b);
k%=mod,b%=mod;
change(i,k,p,b);
}
}
return 0;
}
D NOI2014 魔法森林
为了得到书法大家的真传,小E同学下定决心去拜访住在魔法森林中的隐士。魔法森林可以被看成一个包含个 \(N\) 节点 \(M\) 条边的无向图,节点标号为 \(1..N\) ,边标号为 \(1..M\) 。初始时小E同学在号节点 \(1\) ,隐士则住在号节点 \(N\) 。小E需要通过这一片魔法森林,才能够拜访到隐士。
魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。幸运的是,在号节点住着两种守护精灵:A型守护精灵与B型守护精灵。小E可以借助它们的力量,达到自己的目的。
只要小E带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无向图中的每一条边 \(E_i\) 包含两个权值 \(A_i\) 与 \(B_i\) 。若身上携带的A型守护精灵个数不少于 \(A_i\) ,且B型守护精灵个数不少于 \(B_i\) ,这条边上的妖怪就不会对通过这条边的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向小E发起攻击,他才能成功找到隐士。
由于携带守护精灵是一件非常麻烦的事,小E想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为A型守护精灵的个数与B型守护精灵的个数之和。
解
考虑离线。
从小到达枚举 \(a\) ,用LCT维护MST,对于当前枚举的边,如果边的两个端点已经连通,则找到路径上边权最大的一条边(必须大于当前枚举边权)替换掉
Code
#include<bits/stdc++.h>
#define maxn 200003
#define INF 1050000000
#define o(a) printf(#a": ");for(int j=1;j<=n+m;j++)printf("%d ",a);puts("");
using namespace std;
struct edge{int from,to,a,b;bool operator <(const edge& x)const{return a<x.a;}}e[maxn];
int n,m,f[maxn];
int find(int x){return x!=f[x]?f[x]=find(f[x]):f[x];}
struct node{int mxp,fa,son[2];bool z;}t[maxn];
bool isroot(int p){return t[t[p].fa].son[0]!=p&&t[t[p].fa].son[1]!=p;}
bool chk(int p){return t[t[p].fa].son[1]==p;}
void reverse(int p){if(p==0)return;swap(t[p].son[0],t[p].son[1]);t[p].z^=1;}
void pushup(int p){
int L=t[p].son[0],R=t[p].son[1];
t[p].mxp=p;
if(e[t[L].mxp].b>e[t[p].mxp].b)t[p].mxp=t[L].mxp;
if(e[t[R].mxp].b>e[t[p].mxp].b)t[p].mxp=t[R].mxp;
}
void pushdown(int p){
if(t[p].z){
reverse(t[p].son[0]);
reverse(t[p].son[1]);
t[p].z=0;
}
}
void pushall(int p){if(!isroot(p))pushall(t[p].fa);pushdown(p);}
void rotate(int p){
int q=t[p].fa,r=t[q].fa,k=chk(p),s=t[p].son[!k];
t[q].son[k]=s;
if(s)t[s].fa=q;
if(!isroot(q))t[r].son[chk(q)]=p;
t[p].fa=r;
t[p].son[!k]=q,t[q].fa=p;
pushup(q);
pushup(p);
}
void splay(int p){
pushall(p);
while(!isroot(p)){
int q=t[p].fa;
if(!isroot(q)){
if(chk(p)==chk(q))rotate(q);
else rotate(p);
}
rotate(p);
}
}
void access(int x){for(int y=0;x;y=x,x=t[x].fa)splay(x),t[x].son[1]=y,pushup(x);}
void makeroot(int x){access(x),splay(x),reverse(x);}
void link(int x,int y){
makeroot(x);
t[x].fa=y;
}
void cut(int x,int y){
makeroot(x),access(y),splay(y);
t[x].fa=t[y].son[0]=0;
pushup(y);
}
int query(int x,int y){makeroot(x),access(y),splay(y);return t[y].mxp;}
int main(){
scanf("%d%d",&n,&m);
for(int i=n+1;i<=n+m;i++){
scanf("%d%d%d%d",&e[i].from,&e[i].to,&e[i].a,&e[i].b);
}
sort(e+n+1,e+n+m+1);
for(int i=1;i<=n;i++)f[i]=i;
for(int i=1;i<=n+m;i++)t[i].mxp=i;
int ans=INF;
for(int i=n+1;i<=n+m;i++){
int u=e[i].from,v=e[i].to,fu=find(u),fv=find(v);
// printf("u:%d v:%d i:%d a:%d b:%d\n",u,v,i,e[i].a,e[i].b);
if(fu==fv){
int p=query(u,v);
// printf("# u:%d v:%d p:%d a:%d b:%d\n",e[p].from,e[p].to,p,e[p].a,e[p].b);
if(e[p].b>e[i].b){
cut(e[p].from,p),cut(p,e[p].to);
link(u,i),link(i,v);
}
}
else{
f[fu]=fv;
link(u,i),link(i,v);
}
if(find(1)==find(n)){
ans=min(ans,e[i].a+e[query(1,n)].b);
}
// o(t[j].fa);
}
printf("%d\n",ans==INF?-1:ans);
return 0;
}