Warning:本做法非一般做法。
首先
-
把边权下放成点权(即每个点的颜色表示其到父亲结点的边的颜色),以下内容都是按点权写的。
-
询问的两点称AB,修改时的当前点称X
A与B的LCA称LCA。 -
(UP)数组为当前点上面有可能能修改的最深的节点(默认为该点的父亲)
可以理解为树链剖分的top。一堆废话
简略版
程序分五步
-
离线倒序处理。
-
然后,求每对询问点的LCA。
-
然后,把AB的最短路径拆成A到LCA和B到LCA的两条路径。
-
然后,暴力跳边修改(不修改LCA)。
-
输出答案。
结束了,完结撒花*( ̄▽ ̄)/*。
非常好我啥也没讲(逃)。
详细版
- 好理解,不解释。
- 使用Tarjan算法求LCA。什么?你不知道求LCA的Tarjan算法?戳我学习
- 不解释。
- 你看,两条路径的重叠部分的顶端一定是这两条路径的LCA的深的那个,对不对
不对,没证明没真相。所以我们把(UP_X)设为当前这条路径的顶端(LCA)。因为LCA我们不能修改,所以上面有可能能修改的最深的节点((UP)的定义)为LCA。 - ……
FAQ
我的Tarjan超时了:不可能,那是你的问题- 如果(UP)设为LCA后LCA也被修改了呢?:那是修改LCA那条路径要干的事,跟我有什么关系,跳了修改不了就再跳。
- 怎么跳边?:一直将当前点设为(UP_x)
- 我的Tarjan爆栈了:这正是我接下来要讲的:
人工栈
一般的Tarjan这道题是只能拿80分的,因为这题卡栈。
代码大致模板:
while(栈未空){
if(第一次进栈){ //遍历变量为0,或再弄个数组,如树剖的第二次DFS因为要先遍历重儿子所以要进栈两遍
处理信息 //结点进入时可以自己处理的信息
设置遍历变量
}else{
处理信息 //结点退出后需要其父亲处理的信息
更改遍历变量
}
for(;不能遍历该结点&&遍历变量不为0;更改遍历变量)
if(可以遍历该结点){
栈顶=该结点
处理信息 //结点进入时需要其父亲处理的信息
栈大小++
}else{
处理信息 //结点退出后可以自己处理的信息
栈大小--
}
}
Tarjan核心代码:
//size为栈的大小
//st为栈
//i[x]为栈的第x项的遍历变量
//uni为Tarjan的并查集的合并
//root为并查集的……你懂的
//dp为深度
//b[x][0]下一条边 b[x][1]入点
//a为边的头
//fh为询问的头
//f[x][0]下一个询问 f[x][1]询问的另一点 f[x][2]询问的编号
//lca顾名思义
while(size){
if(!i[size]){
i[size]=a[st[size]]; //设置遍历变量
}else{
uni(b[i[size]][1],st[size]); //结点退出后需要其父亲处理的信息
i[size]=b[i[size]][0]; //更改遍历变量
}
if(i[size]){
dp[b[i[size]][1]]=dp[st[size]]+1; //结点进入时需要其父亲处理的信息
st[size+1]=b[i[size]][1]; //设置栈顶
size++; //增加栈的大小
}else{
for(int i=fh[st[size]];i;i=f[i][0]){ //Tarjan核心代码
int get=root(f[i][1]);
if((get!=f[i][1])||(get==st[size])){
lca[f[i][2]]=get;
}
}
size--; //退栈
}
}
真·完结撒花*( ̄▽ ̄)/*
完整代码
压行编译开关:
1. 滥用for循环,=
2. 允许?运算符
3. 相似数据赋初值压行
#include<cstdio>
#define rg register
#define N 500010
using namespace std;
int n,m,s[N],a[N],fh[N],b[N][2],f[N<<1][3],q[N][3],lca[N],c[N],up[N],dp[N],st[N],i[N]; //数组太多,解释不过来QAQ
int read(){
rg char c=getchar();
for(;c<33;c=getchar());
rg int f=c-48;
for(c=getchar();(c>47)&&(c<58);c=getchar()){
f=(f<<3)+(f<<1)+c-48;
}
return(f);
}
int add(int l,int x,int y){ //l编号 “从x到y的一条边”
b[l][0]=a[x];
b[l][1]=y;
a[x]=l;
}
int fw(int l,int x,int y,int z){ //l编号 “第z次从x到y的LCA的一次查询”
f[l][0]=fh[x];
f[l][1]=y;
f[l][2]=z;
fh[x]=l;
}
int root(int m){
return(s[m]?s[m]=root(s[m]):m);
}
void uni(int x,int y){
s[x]=y;
}
void tarjan(){
int size=1;//此处上方已有注释
st[1]=1;
while(size){
if(!i[size]){
i[size]=a[st[size]];
}else{
uni(b[i[size]][1],st[size]);
i[size]=b[i[size]][0];
}
if(i[size]){
dp[b[i[size]][1]]=dp[st[size]]+1;
st[size+1]=b[i[size]][1];
size++;
}else{
for(rg int i=fh[st[size]];i;i=f[i][0]){
int get=root(f[i][1]);
if((get!=f[i][1])||(get==st[size])){
lca[f[i][2]]=get;
}
}
size--;
}
}
}
void sg(int x,int l,int cl){ //x上面有说 l为LCA cl为修改的颜色
for(int now;dp[x]>dp[l];x=now){ //一定要大于,不能修改LCA
now=up[x];
up[x]=l; //更正确的写法
if(!c[x]){
c[x]=cl;
// up[x]=l; 原写法
}
}
}
int main(){
n=read();m=read();
for(rg int i=2;i<=n;i++){
add(i-1,up[i]=read(),i);
}
for(rg int i=1;i<=m;i++){
q[i][0]=read();q[i][1]=read();q[i][2]=read();
fw((i<<1)-1,q[i][0],q[i][1],i);
fw(i<<1,q[i][1],q[i][0],i);
}
tarjan();
for(rg int i=m;i;i--){
sg(q[i][0],lca[i],q[i][2]);
sg(q[i][1],lca[i],q[i][2]);
}
for(rg int i=2;i<=n;i++){
printf("%d
",c[i]);
}
}
2020/7/24 update:
这个东西时间复杂度其实有问题,可以被卡成 (O(n^2)) ,但这题数据似乎没有为这种奇怪的水法做准备。
2020/7/24 update:
没错是同一天,就差几分钟
在更改up数组时,对每一个经过的点都更改up似乎就不会被卡了。程序已换。
修改了一些因为信息不对等导致的问题