$$ \newcommand{\seq}[2]{{#1}_{1},{#1}_{2},\cdots,{#1}_{#2}} \newcommand{\num}[1]{1,2,\cdots,#1} \newcommand{\stra}[2]{\begin{bmatrix}#1 \\ #2\end{bmatrix}} \newcommand{\strb}[2]{\begin{Bmatrix}#1 \\ #2\end{Bmatrix}} \newcommand{\dw}[1]{\underline{#1}} \newcommand{\up}[1]{\overline{#1}} $$

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.

  1. 修改边权
  2. 询问路径上最大边权

首先把边权转化为点权。两种方法:

  1. 新加 \(n-1\) 个点代表边,点权为对应的边权;

  2. 把边权赋到深度较深的端点的点权上。

  3. access+splay+修改+pushup

  4. makeroot+splay+询问

B.

  1. 询问 \(a\)\(b\) 的距离
  2. 询问 \(a\)\(b\) 的单向路径上的第 \(k\) 个点

  1. access(b)+splay(b)+return t[ch[b][0]].sz
  2. access(b)+splay(b)+splay的kth操作

C.

每个节点有黑白两种颜色。

  1. 改变节点的颜色
  2. 询问 \(a\)\(b\) 的单向路径上的第一个黑色的节点

  1. access+splay+修改
  2. 同splay求前驱操作

D.

有边权

  1. 改变节点颜色
  2. 求树上的最远点对

边权\(→\)点权
本题与上面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.

  1. 改变节点颜色
  2. 询问与节点 \(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. 改变节点颜色
  2. 把颜色相同且相邻的节点看成一个连通块,询问某节点所在连通块大小。

解 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.

有点权

  1. 询问连通块中的最大点权和
  2. 改变节点颜色
  3. 修改点权

综合 \(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]\}\) ,那么:

\[sum[u]=min(s-1,2*(s-t)) \]

举例说明:

  1. 第一种情况
    颜色 \(A×3,B×4,C×5\)
    一组可行解是 \(CACBCACBCBABCACBCACBCBAB\)
    答案是 \(11=12-1\)

  2. 第二种情况
    颜色 \(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\)

  1. \(k_2=1\;and\;b_2!=0\) ,无解
  2. \(k_2=1\;and\;b_2=0\) ,无穷解
  3. \(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;
}

F

$$\color{white}{\text{不做了,做了会死人的}}$$

posted @ 2019-03-23 11:38  chc_1234567890  阅读(235)  评论(0编辑  收藏  举报