仙人掌相关问题的处理方法

目录

  • 仙人掌相关问题的处理方法

    • DFS 树解决仙人掌 DP 问题

      • 引例
      • BZOJ1023cactus仙人掌图SHOI2008
    • 圆方树
      • 定义
      • 构造
      • 性质
      • BZOJ4316 小 C 的独立集
    • 仙人掌最短路问题
      • BZOJ2125最短路
    • 仙人掌剖分
      • UOJ158静态仙人掌
    • 仙人掌分治问题
      • UOJ23跳蚤国王下江南
    • 动态仙人掌
      • 介绍
      • 结构
        • 树上的情况
        • 环上的情况
        • 特殊情况
      • 环上信息的处理
      • 换根操作
      • 实现
      • UOJ65动态仙人掌 III
    • 广义圆方树
      • Tourists
    • 参考文献

如图所示:

仙人掌图就是长得像仙人掌的图嘛(我真没看出哪里像了)

定义:对一个无向连通图,任意一条边属于至多一个简单环。

  • 桥边:非环边,就是连接环的那些边;
  • 环边:就是环中的边嘛。

在仙人掌上,父亲和儿子都有节点的和环的之分。

DFS 树解决仙人掌 DP 问题

仙人掌的处理是十分复杂的(本蒟蒻个人认为,神犇轻喷),这里先从简单的 DFS树开始。

  • 树边:DFS 树中存在的边
  • 非树边:DFS 树中不存在的边

大神们还有什么覆盖之类的定义,参考最后的参考文献。

也就是说环是由多条树边和一条非树边组成的,非树边起到了连接的作用。

我们看几道经典题目:

引例

一棵仙人掌,每条边有边权,求 1 号节点到每个节点的最短路径长度。节点个数 n≤105n\leq10^5

我们先 dfs 一遍的到 dfs 树,这是我们之后处理的基础。

然后从一号节点开始 dp 。假设现在已经求出 1 到 u 的距离,枚举 u 的每一个儿子,如果该儿子不在环内,加上边权继续;否则,枚举换上的我们暴力求环上的每个点到 1 的距离,然后在分别从环上的每个点继续 dp 。

[BZOJ1023]cactus仙人掌图[SHOI2008]

一棵带边权仙人掌,求直径(最短路径最长的两个点的最短路径长度)。节点个数 n≤105n\leq10^5

我们 DFS 可以得到一颗 DFS 树。这里的 DFS 与 Tarjan 比较类似(大概就是 Tarjan),对于现在访问的节点 u 我们记录下来 low[u],dfn[u],dpt[u],fa[u] (前两个含义参考 Tarjan ,dpt 是深度(deepth) , fa 是 u 在 DFS 树上的父亲),对于没访问的(即 dfn 为 0 的)节点继续递归,向上回溯时更新 low 值。

遍历每条边时如果 low[v] 大于 dfn[u] ,说明此边为树边;对于一个节点,若其某个儿子在 DFS 树上的父亲不是它(没错,它的儿子的父亲不是它,机房的小伙伴们都笑疯了),那么说明这里出现了一个环,且此节点为环的(深度最小的节点),它的那个儿子为环的(环中最后被遍历到的,也就是 DFS 树的叶子节点)

我们做以上这些的目的主要就是判断环和桥,然后分别 DP 处理。


我们定义 f[i]f[i] 为以 i 为端点的最长链的长度。

对于来说十分简单(此时假设没有环):

ans=max{ansmax(f[v])+second_max(f[v])+1, v is son of u

ans=max\begin{cases} ans\\max(f[v])+\text{second_}max(f[v])+1,\ \text{v is son of u}\end{cases}

f[u]=max(f[v])+1, v is son of u

f[u]=max(f[v])+1,\ \text{v is son of u}

这里我们在代码中的写法稍有不同:

  • 先更新 ans ,后更新 f[u]。
  • 遍历所有儿子,对每个儿子,f[u] 中存的可能是(我们只考虑是的情况,因为只有此时才对结果有影响,这也是为什么上一条要确保) second_max(f[v])\text{second_}max(f[v]) ,而 f[v] 可能是(依旧只考虑是的情况) max(f[v])max(f[v]),所以我们只需用 f[u]+f[v]+1f[u]+f[v]+1 来更新 ans 即可;

现在我们考虑环的问题:

对于一个环,我们的宗旨是把它缩成一个点(即把一个环的 f 信息都存在其根上),然后就可以开心地按之前的方式 DP 啦!

由于环中更新答案的时候只转一圈不能保证答案最优(因为有可能最优的那一部分环被根分开了),又由于环中距离的定义是最短路,所以我们只要转够一圈半即可。

如图:

定义环的节点集合为 C ,记环中一点 a 在环中的遍历顺序为 aid 、环长(环中节点个数)为 L ,则在环中两个点之间的距离

dis(a,b)=min{abs(aid−bid)L−ans(aid−bid),a,b∈C

dis(a,b)=min\begin{cases}abs(aid-bid)\\L-ans(aid-bid)\end{cases},\quad a,b \in C

ans=max{ansf[a]+f[b]+dis(a,b)

ans=max\begin{cases}ans\\f[a]+f[b]+dis(a,b)\end{cases}

我们记一个环的根为 x 、尾为 y。(我只是懒得起名)

f[x]=max(f[i]+dis(x,i)),i∈∁{x}C

f[x]=max(f[i]+dis(x,i)),\quad i\in \complement_{\{x\}}C

实现的时候,我们把环中的节点的 f 依次存在一个数组(我的代码中是 a 数组懒得起名+1)里,然后将这个数组倍长(就是将其复制一遍放到尾部),遍历时动一定一(这里莫名怀念小晓笑潇),若之间相差大于 L2\frac{L}{2} 就 continue,再用单调队列优化一下, ans 就能轻松更新好了。之后再按照公式更新 f[x] 即可。

整个算法过程的时间复杂度仅为 O(N)O(N) ,还是蛮快的。


/**************************************************************Problem: 1023User: zhangche0526Language: C++Result: AcceptedTime:256 msMemory:7076 kb
****************************************************************/#include<iostream>
#include<cstdio>
using namespace std;const int MAXN=5e4+5,MAXM=MAXN<<1;int n,m;
struct E{int next,to;} e[MAXM<<1];int ecnt,G[MAXN];
void addEdge(int u,int v)
{e[++ecnt]=(E){G[u],v};G[u]=ecnt;e[++ecnt]=(E){G[v],u};G[v]=ecnt;
}
int ans;
int f[MAXN];
int dfn[MAXN],dcnt,low[MAXN],dpt[MAXN],fa[MAXN];
int que[MAXN<<1],a[MAXN<<1];
void solve(int x,int y)
{int cnt=dpt[y]-dpt[x]+1,head=1,tail=1,i;for(i=y;i!=x;i=fa[i]) a[cnt--]=f[i];a[1]=f[x];cnt=dpt[y]-dpt[x]+1;for(i=1;i<=cnt;i++) a[i+cnt]=a[i];que[1]=1;for(i=2;i<=cnt+(cnt>>1);i++){if(i-que[head]>(cnt>>1)) head++;ans=max(ans,a[i]+i+a[que[head]]-que[head]);while(head<=tail&&a[i]-i>=a[que[tail]]-que[tail]) tail--;que[++tail]=i;}for(i=2;i<=cnt;i++) f[x]=max(f[x],a[i]+min(i-1,cnt-i+1));
}void dfs(int u)
{int i;dfn[u]=low[u]=++dcnt;for(i=G[u];i;i=e[i].next){int v=e[i].to;if(v==fa[u]) continue;if(!dfn[v]) {fa[v]=u;dpt[v]=dpt[u]+1;dfs(v);}low[u]=min(low[u],low[v]);if(low[v]>dfn[u]) ans=max(ans,f[u]+f[v]+1),f[u]=max(f[u],f[v]+1);//对树边的更新}for(i=G[u];i;i=e[i].next){int v=e[i].to;if(fa[v]!=u&&dfn[u]<dfn[v])//遍历非树边,处理环solve(u,v);}
}int main()
{int i,j;scanf("%d%d",&n,&m);for(i=1;i<=m;i++){int k;scanf("%d",&k);int u,v;scanf("%d",&u);for(j=2;j<=k;j++) scanf("%d",&v),addEdge(u,v),u=v;}dfs(1);printf("%d\n",ans);return 0;
}

圆方树

通过前面的几道例题,我们发现:其实解决仙人掌 DP 问题不过就是参照树上的解法然后对环上的情况特殊处理一下,把环的信息记录到一个点上。

其实,神犇们很早就发现了这一点,于是他们想:既然仙人掌的许多问题在树上都有现成的解法,那么如果直接把仙人掌变成树,岂不美哉?

于是,神犇们成功的仙人掌变成树,并给这种树起了一个生动形象的名字:圆方树,它能解决大多数静态仙人掌问题

定义

仙人掌 G=(V,E)G=(V,E) 的圆方树 T=(VT,ET)T=(V_T,E_T) 为满足一下条件的无向图:

  • VT=RT∪ST,RT=V,RT∩ST=ϕV_T=R_T\cup S_T,R_T=V,R_T\cap S_T=\phi ,我们称 RTR_T 集合为圆点, STS_T 集合为方点;
  • ∀e∈E\forall e\in E ,若 ee 不在任何简单环中,则 e∈ETe\in E_T;

    易证:圆方树是一棵树

构造

  • 从任意一个点跑 Tarjan 求点双连通分量;
  • 对于每个点双,从栈中取出,这时栈中的顺序就是环上的顺序,在圆方树中建立方点,依次向栈中的圆点连边;
  • 如果这条边是桥边,我们直接在圆方树中加入它。

性质

  • 两个方点不会相连
  • 圆方树是无根树

    子仙人掌:以 r 为根的仙人掌上的点 p 的子仙人掌是从仙人掌中起吊 p 到 r 的简单路径上的所有边后, p 所在的连通块。

  • 以 r 为根的仙人掌中点 p 的子仙人掌就是圆方树以 r 为根时点 p 的子树中的所有圆点。

[BZOJ4316] 小 C 的独立集

在一个无向连通图中选出若干个点,这些点互相没有边连接,并使取出的点尽量多。数据保证图的一条边属于且仅属于一个简单环,图中没有重边和自环。点数 n≤106n\leq10^6 ,边数 m≤106m\leq10^6

    在一个无向连通图中选出若干个点,这些点互相没有边连接,并使取出的点尽量多。数据保证图的一条边属于且仅属于一个简单环,图中没有重边和自环。点数 $n\leq10^6$ ,边数 $m\leq10^6$
  • 类比树上的解法:设 f[i][0/1]f[i][0/1] 表示点 i 是否选时子树内的最大独立集;
  • 如果一条边连接两个圆点,用树上转移方式即可;
  • 而对于连接圆点和方点的情况,把这个换中所有点拿出来,跑一个环上的 DP 。

时间复杂度: O(n)O(n)

其实我们在解这道题的时候,没有必要真正建出圆方树,我在代码里建出圆方树只是为了举例说明,是为让大家熟悉圆方树的建法。

/**************************************************************Problem: 4316User: zhangche0526Language: C++Result: AcceptedTime:204 msMemory:12140 kb
****************************************************************/#include<iostream>
#include<cstdio>
#include<cstring>const int MAXN=2e5+5,MAXM=2e5+5,INF=~0U>>1;int n,m,newn;//newn:圆方树的点数struct CFS
{struct E{int next,to;} e[MAXM];int ecnt,G[MAXN];void addEdge(int u,int v){e[++ecnt]=(E){G[u],v};G[u]=ecnt;}void addEdge2(int u,int v){addEdge(u,v);addEdge(v,u);}CFS(){ecnt=1;}
} G,T;int f[MAXN][2],g[MAXN][2],gcnt;
void treeDP(int u,int from)
{int i;if(u<=n){f[u][0]=0;f[u][1]=1;for(i=T.G[u];i;i=T.e[i].next){int v=T.e[i].to;if(v==from) continue;treeDP(v,u);if(v>n) continue;f[u][0]+=std::max(f[v][0],f[v][1]);f[u][1]+=f[v][0];}}else{for(i=T.G[u];i;i=T.e[i].next)if(T.e[i].to!=from)treeDP(T.e[i].to,u);gcnt=0;for(i=T.G[u];i;i=T.e[i].next){g[++gcnt][0]=f[T.e[i].to][0];g[gcnt][1]=f[T.e[i].to][1];}for(i=gcnt-1;i;i--){g[i][0]+=std::max(g[i+1][0],g[i+1][1]);g[i][1]+=g[i+1][0];}f[from][0]=g[1][0];gcnt=0;for(i=T.G[u];i;i=T.e[i].next){g[++gcnt][0]=f[T.e[i].to][0];g[gcnt][1]=f[T.e[i].to][1];}g[gcnt][1]=-INF;for(i=gcnt-1;i;i--){g[i][0]+=std::max(g[i+1][0],g[i+1][1]);g[i][1]+=g[i+1][0];}f[from][1]=g[1][1];}
}int fa[MAXN],dfn[MAXN],dcnt;
bool onRing[MAXN];
void dfs(int u,int la)
{int i,j;dfn[u]=++dcnt;for(i=G.G[u];i;i=G.e[i].next){int v=G.e[i].to;if(v==la) continue;if(!dfn[v]){fa[v]=u;onRing[u]=false;dfs(v,u);if(!onRing[u]) T.addEdge2(u,v);}else{if(dfn[v]>dfn[u]) continue;for(j=u,++newn;j!=fa[v];j=fa[j])T.addEdge2(newn,j),onRing[j]=true;}}
}int main()
{int i,u,v;scanf("%d%d",&n,&m);newn=n;for(i=1;i<=m;i++){scanf("%d%d",&u,&v);G.addEdge2(u,v);}dfs(1,0);treeDP(1,0);printf("%d\n",std::max(f[1][0],f[1][1]));
}

仙人掌最短路问题

[BZOJ2125]最短路

一个带权仙人掌图,求多源最短路。点数 N≤105N\leq10^5 ,边数 M≤105M\leq10^5 ,询问个数 Q≤105Q\leq10^5

对多源最短路问题,树上的经典做法是两点到根的距离减去两倍的 LCA 到根的距离,那么我们使用圆方树将仙人掌图转化为树后就可以按相同思路解决了。

然而我们发现原图的边权并没有很好地表现在我们建出来的圆方树上,所以我们首先解决边权问题:

  1. 找到换的根;
  2. 方点向环的根的连边的边权为 0 ;
  3. 环上其他点向方点连边,边权为到环的根的最短路径长度。

计算好边权后,对于两点的 LCA 是圆点的情况就可以直接按照树上的方法求距离了;而对于两点的 LCA 是方点的情况,其 LCA 是一个环,这两点一直向祖先走会到环上的两个不同位置,我们只需找到这两个位之间的到最短路。

具体方法:

  1. Tarjan 找出所有的简单环,并记录环上信息备用,建圆方树;

    这里需要注意,上一道题的图比仙人掌图还要特殊,每个节点一定属于一个环,因此我们可以对于每个节点只访问一次即可;可对于一般情况,我们在发现一个点 u 的下一个点 v 满足: low[v]=dfn[u]low[v]=dfn[u] 时,说明我们找到了一个环,且 u 为环首,这时我们要弹出从环尾到 v 的所有点,然而我们并不能确定是否要弹出 u (因为如果 u 还属于另外一个环,那就悲剧了),所以这是我们还有走回头路更新 low 值,判断是否弹出 v 。

    其实这里我们还有一种更好实现的处理方法:在 Tarjan 时维护一个边的栈,这样就可以免去许多细节处理问题。

  2. 维护圆方树的倍增数组,记录每个点到根的距离。

  3. 倍增法求 LCA,若果 LCA 是环,还需要把两个点倍增到这个环上,在环上查询。

第一种实现 by PoPoQQQ

/**************************************************************Problem: 2125User: PoPoQQQLanguage: C++Result: AcceptedTime:684 msMemory:5248 kb
****************************************************************/#include <map>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 10100
#define INF 0x3f3f3f3f
using namespace std;int n,m,q,cnt;map<int,int> f[M];
vector<int> belong[M];
int dis[M];vector<int> rings[M];
int size[M];
map<int,int> dist[M];void Add(int x,int y,int z)
{if( f[x].find(y)==f[x].end() )f[x][y]=INF;f[x][y]=min(f[x][y],z);
}
namespace Cactus_Graph{int fa[M<<1][16],dpt[M<<1];pair<int,int> second_lca;int Get_Depth(int x){if(!fa[x][0]) dpt[x]=1;if(dpt[x]) return dpt[x];return dpt[x]=Get_Depth(fa[x][0])+1;}void Pretreatment(){int i,j;for(j=1;j<=15;j++){for(i=1;i<=cnt;i++)fa[i][j]=fa[fa[i][j-1]][j-1];for(i=n+1;i<=n<<1;i++)fa[i][j]=fa[fa[i][j-1]][j-1];}for(i=1;i<=cnt;i++)Get_Depth(i);for(i=n+1;i<=n<<1;i++)Get_Depth(i);}int LCA(int x,int y){int j;if(dpt[x]<dpt[y])swap(x,y);for(j=15;~j;j--)if(dpt[fa[x][j]]>=dpt[y])x=fa[x][j];if(x==y) return x;for(j=15;~j;j--)if(fa[x][j]!=fa[y][j])x=fa[x][j],y=fa[y][j];second_lca=make_pair(x,y);return fa[x][0];}
}
void Tarjan(int x)
{static int dpt[M],low[M],T;static int stack[M],top;map<int,int>::iterator it;dpt[x]=low[x]=++T;stack[++top]=x;for(it=f[x].begin();it!=f[x].end();it++){if(dpt[it->first])low[x]=min(low[x],dpt[it->first]);else{Tarjan(it->first);if(low[it->first]==dpt[x]){int t;rings[++cnt].push_back(x);belong[x].push_back(cnt);Cactus_Graph::fa[cnt][0]=n+x;do{t=stack[top--];rings[cnt].push_back(t);Cactus_Graph::fa[n+t][0]=cnt;}while(t!=it->first);}low[x]=min(low[x],low[it->first]);}}
}
void DFS(int x)
{vector<int>::iterator it,_it;static int stack[M];int i,j,top=0;for(it=rings[x].begin();it!=rings[x].end();it++)stack[++top]=*it;stack[++top]=*rings[x].begin();for(i=1;i<top;i++){int p1=stack[i],p2=stack[i+1];size[x]+=f[p1][p2];if(i!=top-1)dist[x][p2]=dist[x][p1]+f[p1][p2];}i=2;j=top-1;while(i<=j){if(dis[stack[i-1]]+f[stack[i-1]][stack[i]]<dis[stack[j+1]]+f[stack[j+1]][stack[j]])dis[stack[i]]=dis[stack[i-1]]+f[stack[i-1]][stack[i]],i++;elsedis[stack[j]]=dis[stack[j+1]]+f[stack[j+1]][stack[j]],j--;}for(_it=rings[x].begin(),_it++;_it!=rings[x].end();_it++)for(it=belong[*_it].begin();it!=belong[*_it].end();it++)DFS(*it);
}
int main()
{using namespace Cactus_Graph;int i,x,y,z;cin>>n>>m>>q;for(i=1;i<=m;i++){scanf("%d%d%d",&x,&y,&z);Add(x,y,z);Add(y,x,z);}Tarjan(1);Pretreatment();vector<int>::iterator it;for(it=belong[1].begin();it!=belong[1].end();it++)DFS(*it);for(i=1;i<=q;i++){scanf("%d%d",&x,&y);int lca=LCA(n+x,n+y);if(lca>n) printf("%d\n",dis[x]+dis[y]-2*dis[lca-n]);else{int ans=dis[x]+dis[y]-dis[second_lca.first-n]-dis[second_lca.second-n];int temp=abs(dist[lca][second_lca.first-n]-dist[lca][second_lca.second-n]);ans+=min(temp,size[lca]-temp);printf("%d\n",ans);}}return 0;
}

第二种实现 by virgil

/**************************************************************Problem: 2125User: zhangche0526Language: C++Result: AcceptedTime:432 msMemory:21816 kb
****************************************************************/#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<stack>
#include<queue>const int MAXN=1e4+5;int N,M,Q;int abs(int x){return (x<0)?(-x):x;}struct E{int next,to,val;} e[MAXN<<3];int ecnt,G[MAXN];
void addEdge(int u,int v,int w){e[++ecnt]=(E){G[u],v,w};G[u]=ecnt;}
void addEdge2(int u,int v,int w){addEdge(u,v,w);addEdge(v,u,w);}
void initCFS(){memset(G,0,sizeof(G));ecnt=0;}
struct A{int u,v,w;A(int u=0,int v=0,int w=0):u(u),v(v),w(w){};};int dis[MAXN];bool inQ[MAXN];
std::queue<int> que;
void SPFA(int S)
{int i;memset(dis,0x3f,sizeof(dis));que.push(S);dis[S]=0;inQ[S]=true;while(!que.empty()){int u=que.front();que.pop();inQ[u]=false;for(i=G[u];i;i=e[i].next){int v=e[i].to;if(dis[v]>dis[u]+e[i].val){dis[v]=dis[u]+e[i].val;if(!inQ[v]) que.push(v),inQ[v]=true;}}}
}std::stack<A> st;
int ringLen[MAXN],rcnt;
int belong[MAXN],ringRtDis[MAXN];
int anc[MAXN][20];
void addRing(int u,int v)
{rcnt++;while(st.top().u!=u&&st.top().v!=v){A a=st.top();st.pop();ringRtDis[a.u]=ringRtDis[a.v]+a.w;ringLen[rcnt]+=a.w;if(a.u!=u) belong[a.u]=rcnt,anc[a.u][0]=u;if(a.v!=u) belong[a.v]=rcnt,anc[a.v][0]=u;}A a=st.top();st.pop();ringRtDis[a.u]=ringRtDis[a.v]+a.w;ringLen[rcnt]+=a.w;anc[a.v][0]=a.u;
}int dfn[MAXN],low[MAXN],dcnt;
void tarjan(int u,int la)
{dfn[u]=low[u]=++dcnt;for(int i=G[u];i;i=e[i].next){int v=e[i].to;if(v==la) continue;if(!dfn[v]){st.push(A(u,v,e[i].val));tarjan(v,u);low[u]=std::min(low[u],low[v]);if(low[v]>=dfn[u])addRing(u,v);}else if(dfn[v]<low[u]) low[u]=dfn[v],st.push(A(u,v,e[i].val));}
}int dpt[MAXN];
int rebuild(int u,int la)
{dpt[u]=dpt[la]+1;for(int i=G[u];i;i=e[i].next)rebuild(e[i].to,u);
}inline void initLCA()
{for(int i=1;(1<<i)<=N;i++)for(int j=1;j<=N;j++)anc[j][i]=anc[anc[j][i-1]][i-1];
}int calDis(int x,int y)
{int i;if(dpt[x]<dpt[y]) std::swap(x,y);int xDis=dis[x],yDis=dis[y];int maxlogn=std::floor(std::log(N)/std::log(2));for(i=maxlogn;i>=0;i--)if(dpt[x]-(1<<i)>=dpt[y])x=anc[x][i];if(x==y) return xDis-dis[x];for(i=maxlogn;i>=0;i--)if(anc[x][i]!=anc[y][i])x=anc[x][i],y=anc[y][i];if(belong[x]&&belong[x]==belong[y]){int xyDis=abs(ringRtDis[x]-ringRtDis[y]);int minDis=std::min(xyDis,ringLen[belong[x]]-xyDis);return xDis+yDis-dis[x]-dis[y]+minDis;}else return xDis+yDis-2*dis[anc[x][0]];
}int main()
{   int i;scanf("%d%d%d",&N,&M,&Q);for(i=1;i<=M;i++){int u,v,w;scanf("%d%d%d",&u,&v,&w);addEdge2(u,v,w);}SPFA(1);tarjan(1,0);initLCA();initCFS();for(i=2;i<=N;i++)addEdge(anc[i][0],i,0);rebuild(1,0);while(Q--){int x,y;scanf("%d%d",&x,&y);printf("%d\n",calDis(x,y));}return 0;
}

仙人掌剖分

[UOJ158]静态仙人掌

给出一棵根为 1 的仙人掌,每个点是黑色或者白色,保证所有环都是奇环,要求支持三种操作:

  1. 把一个点到根的最短路径上的所有点颜色取反;
  2. 把一个点到根的最长简单路径上的所有点颜色取反;
  3. 询问一个点的子仙人掌里面有多少个黑点。

树上的做法肯定是树剖,对于仙人掌,我们对圆方树进行树剖。

根据圆方树的性质,子仙人掌就是圆方树的子树,因此我们可以对树剖的方法加以扩充解决本问题。

我们回想树剖,其精髓就是支持树上重链的快速操作,而对于仙人掌来说,我们同样需要支持对重链的快速操作:就是支持对一条重链的一个前缀的最长路或最短路进行操作。

那么我们就可以把点进行分类。考虑每一条重链,将点分成 3 类:

  1. 割点
  2. 在重链上作为最短路径出现的点
  3. 在重链上作为最长路径出现的点。

那么显然对于第 1 类点,直接在树上修改即可。然而对于后两种点,树上路径不一定会扫过需要更改的点。

按照传统的剖分方法的话是不支持考虑方点连出的圆点的,因此我们考虑在 DFS 序中,如果一个点是方点,那么我们先访问它的所有儿子,然后再按照重边先行的顺序访问儿子的子树(不包括那些儿子了),这样的话,我们修改一段区间的时候,就会扫过这个环上所有点;而我们已经把点分类了,因此也可以选择只修改最短路径或最长路径上的点。

代码 by laofu

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<queue>
#include<set>
#define md double
#define LL long long
#define LLD "%lld"
using namespace std;
const int N=2e5+100;
const int inf=2147483647;
int gi()
{int w=0;bool q=1;char c=getchar();while ((c<'0'||c>'9') && c!='-') c=getchar();if (c=='-') q=0,c=getchar();while (c>='0'&&c <= '9') w=w*10+c-'0',c=getchar();return q? w:-w;
}
int head[N],next[N<<1],to[N<<1],tot;
int fa[N],n,dis[N],rt[N],scc,in[N],col[N];
int siz[N],son[N],top[N],l[N],r[N],dfn[N],sa[N],R[N],L[N];
int all[N<<2][3],c[N<<2][3];bool tag[N<<2][3];
#define lc (i<<1)
#define rc (i<<1|1)
inline void build(int i,int l,int r) {if (l==r) { if (sa[l]<=n) all[i][col[sa[l]]]++; }else {int m=(l+r)>>1;build(lc,l,m);build(rc,m+1,r);all[i][0]=all[lc][0]+all[rc][0];all[i][1]=all[lc][1]+all[rc][1];all[i][2]=all[lc][2]+all[rc][2];}c[i][0]=all[i][0],c[i][1]=all[i][1],c[i][2]=all[i][2];
}
#define pushdown(i) for (m=0;m<3;m++) if (tag[i][m]) tag[lc][m]^=1,c[lc][m]=all[lc][m]-c[lc][m],tag[rc][m]^=1,c[rc][m]=all[rc][m]-c[rc][m],tag[i][m]=0;
inline void modify(int i,int l,int r,int L,int R,int p) {if (L<=l&&r<=R) {tag[i][0]^=1;c[i][0]=all[i][0]-c[i][0];if (p!=2) tag[i][1]^=1,c[i][1]=all[i][1]-c[i][1];if (p!=1) tag[i][2]^=1,c[i][2]=all[i][2]-c[i][2];return;}int m;pushdown(i);m=(l+r)>>1;if (L<=m) modify(lc,l,m,L,R,p);if (m<R) modify(rc,m+1,r,L,R,p);c[i][0]=c[lc][0]+c[rc][0];c[i][1]=c[lc][1]+c[rc][1];c[i][2]=c[lc][2]+c[rc][2];
}
inline int query(int i,int l,int r,int L,int R) {if (L<=l&&r<=R) return c[i][0]+c[i][1]+c[i][2];int m;pushdown(i);m=(l+r)>>1;if (R<=m) return query(lc,l,m,L,R);if (m<L) return query(rc,m+1,r,L,R);return query(lc,l,m,L,R)+query(rc,m+1,r,L,R);
}
namespace tree{int head[N],next[N<<1],to[N<<1],tot;inline void link(int a,int b) { to[++tot]=b,next[tot]=head[a],head[a]=tot; }inline void dfs1(int k) {siz[k]=1;for (int i=head[k];i;i=next[i]) {dfs1(to[i]);siz[k]+=siz[to[i]];if (siz[to[i]]>siz[son[k]]) son[k]=to[i];}}inline void dfs2(int k) {if (!dfn[k]) sa[dfn[k]=++tot]=k; else if (k<=n) L[k]=tot+1; if (!son[k]) { R[k]=tot; return; }int i,tp;if (k>n) {for (i=head[k];i;i=next[i]) dis[to[i]]=++dis[k];L[k]=tot+1;for (i=head[k],tp=1+(dis[son[k]]>dis[k]>>1);i;i=next[i]) if (to[i]==son[k]) tp^=3; else col[to[i]]=tp,sa[dfn[to[i]]=++tot]=to[i];R[k]=tot;sa[dfn[son[k]]=++tot]=son[k];}top[son[k]]=top[k];dfs2(son[k]);for (i=head[k];i;i=next[i]) if (to[i]!=son[k]) top[to[i]]=to[i],dfs2(to[i]);if (k<=n) R[k]=tot;}inline void rev(int k,int p) {while (k)if (top[k]==k)if (rt[k]) {if ((dis[k]<=dis[rt[k]]>>1)==(p==1)) {modify(1,1,scc,L[rt[k]],dfn[k],0);if (dis[son[rt[k]]]<dis[k]) modify(1,1,scc,dfn[son[rt[k]]],dfn[son[rt[k]]],0);}else {modify(1,1,scc,dfn[k],R[rt[k]],0);if (dis[son[rt[k]]]>dis[k]) modify(1,1,scc,dfn[son[rt[k]]],dfn[son[rt[k]]],0);}k=rt[rt[k]];}else modify(1,1,scc,dfn[k],dfn[k],p),k=fa[k];else if (top[k]<=n&&rt[top[k]]) modify(1,1,scc,dfn[son[top[k]]],dfn[k],p),k=top[k];else modify(1,1,scc,top[k]<=n?dfn[top[k]]:L[top[k]],dfn[k],p),k=fa[top[k]];}inline int ask(int k) { return !rt[k]||k==son[rt[k]]?query(1,1,scc,dfn[k],R[k]):query(1,1,scc,dfn[k],dfn[k])+(L[k]<=R[k]?query(1,1,scc,L[k],R[k]):0); }
}
inline void dfs(int k) {dfn[k]=++tot;for (int i=head[k],t,p;i;i=next[i])if (!dfn[to[i]]) {fa[to[i]]=k,dfs(to[i]);if (!rt[to[i]]) tree::link(k,to[i]);}else if (to[i]!=fa[k]&&dfn[to[i]]<dfn[k]) {rt[p=++scc]=to[i];tree::link(to[i],scc);fa[scc]=to[i];for (t=k;t!=to[i];t=fa[p=t]) l[t]=fa[t],r[t]=p,tree::link(scc,t),rt[t]=scc;l[scc]=p,r[scc]=k;}
}
int main()
{//freopen("cactus.in","r",stdin);//freopen("cactus.out","w",stdout);n=scc=gi();int m=gi(),Q=gi(),k,tot=0,a,b;while (m--) {a=gi(),b=gi();to[++tot]=b,next[tot]=head[a],head[a]=tot;to[++tot]=a,next[tot]=head[b],head[b]=tot;}dfs(1);for (k=1;k<=n;dfn[k++]=0) if (rt[k]) fa[k]=rt[k];top[1]=1;tree::tot=0;tree::dfs1(1);tree::dfs2(1);build(1,1,scc);while (Q--) {if ((k=gi())==3) printf("%d\n",tree::ask(gi()));else tree::rev(gi(),k);}   return 0;
}

仙人掌分治问题

[UOJ23]跳蚤国王下江南

一个仙人掌,求对 i∈[1,n)i\in[1,n) 输出从 1 出发的长度为 ii 的简单路径有多少条。答案对 998244353 取模。点数 N≤105N\leq 10^5

我们先考虑暴力 DP 算法:

记 f[u][l]f[u][l] 为从节点 u 开始,只能向远离根的方向走,长度为 ll 的路径条数。

那么对于 u 的每个儿子 v ,如果 v 是一个节点,就把 f[v][l−1]f[v][l-1] 加到 f[u][l]f[u][l] 里;如果是一个环,那么枚举这个换上的每个儿子 z ,把 f[z][l−d1]+f[z][l−d2]f[z][l-d_1]+f[z][l-d_2] 加到 f[u][l]f[u][l] 里(其中 d1,d2d_1,d_2 表示 x 到 z 的两条路径的长度 )。

不过上述算法是 A 不掉的,我们发现, f[u]f[u] 可以看做一个多项式,每次从 u 的一个儿子 v 注意过来时,相当于是把 f[v]f[v] 乘上 xx 或乘上 xd1+xd2x^{d_1}+x^{d_2} 并加到 f[u]f[u] 中。

首先是分治,每次分治时,找到重心 u 以后,分治每个连通块,然后将根到 u 的多项式求出来,记为 f(x)f(x) ,再将 u 的所有儿子的答案求出来相加,记为 g(x)g(x) 。 我们只要将 f(x)⋅g(x)f(x)\cdot g(x) 和每个连同快当然答案加起来即可。

我们在求 f(x)f(x) 时,如果将重心到根的多项式用分治+ FFT 乘起来起来,总的时间复杂度为 O(Nlog32N)O(Nlog_2^3N) (这里点分治 log2Nlog_2N ,分治 log2Nlog_2N , FFT Nlog2NNlog_2N ),然而这样的话我们并没有充分利用信息,而导致了多余的计算:分治是不必要的,我们只需在点分治的时候记录一下根到重心的的多项式,然后一个一个乘回去,这样就可以在 O(Nlog2N)O(Nlog_2N) 的时间复杂度内完美地解决问题。

代码 by Vfleaking

#include <iostream>
#include <cstdio>
#include <cassert>
#include <cstdlib>
#include <climits>
#include <algorithm>
#include <map>
#include <vector>
using namespace std;typedef long long s64;const int P = 998244353;const int MaxN = 100000;
const int MaxM = MaxN * 2;const int FFT_G = 3;
const int MaxFFTN = 262144;template <class T>
inline void relax(T &a, const T &b)
{if (b > a)a = b;
}inline int &modaddto(int &a, const int &b)
{a += b;if (a >= P)a -= P;return a;
}
inline int modpow(int a, const int &n)
{int res = 1;int t = a;for (int i = n; i > 0; i >>= 1){if (i & 1)res = (s64)res * t % P;t = (s64)t * t % P;}return res;
}struct polyarray
{int *a;int n;inline polyarray(){n = 0;a = NULL;}inline polyarray(const polyarray &rhs){n = rhs.n;a = new int[n];copy(rhs.a, rhs.a + n, a);}inline ~polyarray(){dispose();}inline void dispose(){if (a){n = 0;delete []a;a = NULL;}}int getDegree() const{int d = n - 1;while (d >= 0 && a[d] == 0)d--;return d;}void resize(int l){if (l == 0){dispose();return;}int new_n = 1;while (new_n < l)new_n <<= 1;if (new_n == n){fill(a + new_n, a + n, 0);return;}int *new_a = new int[new_n];int tl = min(n, new_n);copy(a, a + tl, new_a);fill(new_a + tl, new_a + new_n, 0);if (a != NULL)delete []a;n = new_n;a = new_a;}inline polyarray& operator=(const polyarray &rhs){dispose();n = rhs.n;a = new int[n];copy(rhs.a, rhs.a + n, a);return *this;}inline void print(){for (int i = 0; i < n; i++)cout << a[i] << " ";cout << endl;}
};int preGPow[MaxFFTN];void fft(int *a, int n, int s, int *out)
{if (n == 1){out[0] = a[0];return;}int m = n >> 1;fft(a, m, s + 1, out);fft(a + (1 << s), m, s + 1, out + m);for (int i = 0, *po = out, *pe = out + m; i < m; i++, pe++, po++){int o = *po, e = (s64)*pe * preGPow[i << s] % P;*po = (o + e) % P;*pe = (o + P - e) % P;}
}inline polyarray &polymulto(polyarray &a, const polyarray &b)
{int a_d = a.getDegree(), b_d = b.getDegree();if (a_d == -1 || b_d == -1){a.dispose();return a;}int l = a_d + b_d + 1;static int da[MaxFFTN];static int db[MaxFFTN];int b1_n = 0;for (int i = 0; i <= b_d; i++)b1_n += b.a[i] != 0;if (b1_n <= 2){for (int i = 0; i < l; i++)da[i] = 0;for (int j = 0; j <= b_d; j++)if (b.a[j] != 0)for (int i = 0; i <= a_d; i++)da[i + j] = (da[i + j] + (s64)a.a[i] * b.a[j]) % P;a.resize(l);copy(da, da + l, a.a);return a;}int a1_n = 0;for (int i = 0; i <= a_d; i++)a1_n += a.a[i] != 0;if (a1_n <= 2){for (int i = 0; i < l; i++)da[i] = 0;for (int i = 0; i <= a_d; i++)if (a.a[i] != 0)for (int j = 0; j <= b_d; j++)da[i + j] = (da[i + j] + (s64)a.a[i] * b.a[j]) % P;a.resize(l);copy(da, da + l, a.a);return a;}int tn = 1;while (tn < l)tn <<= 1;int curG = modpow(FFT_G, (P - 1) / tn);preGPow[0] = 1;for (int i = 1; i < tn; i++)preGPow[i] = (s64)preGPow[i - 1] * curG % P;a.resize(tn);fft(a.a, tn, 0, da);copy(b.a, b.a + b_d + 1, a.a);fill(a.a + b_d + 1, a.a + tn, 0);fft(a.a, tn, 0, db);for (int i = 0; i < tn; i++)da[i] = (s64)da[i] * db[i] % P;reverse(preGPow + 1, preGPow + tn);fft(da, tn, 0, a.a);int revTN = modpow(tn, P - 2);for (int i = 0; i < tn; i++)a.a[i] = (s64)a.a[i] * revTN % P;return a;
}
inline polyarray &polyaddto(polyarray &a, const polyarray &b, int off = 0)
{int b_d = b.getDegree();if (a.n <= b_d + off)a.resize(b_d + off + 1);for (int i = b_d; i >= 0; i--)modaddto(a.a[i + off], b.a[i]);return a;
}struct halfEdge
{int u;halfEdge *next;
};
halfEdge adj_pool[MaxM * 2], *adj_tail = adj_pool;int n, m;
halfEdge *adj[MaxN + 1];inline void addEdge(int v, int u)
{adj_tail->u = u, adj_tail->next = adj[v], adj[v] = adj_tail++;
}typedef pair<halfEdge**, halfEdge*> recoverEdgeType;inline recoverEdgeType delEdge(int v, int u)
{for (halfEdge *e = adj[v], **prev = &adj[v]; e; prev = &e->next, e = e->next)if (e->u == u){*prev = e->next;return recoverEdgeType(prev, e);}assert(false);
}
inline void recoverEdge(const recoverEdgeType &r)
{*r.first = r.second;
}
inline halfEdge *oppoE(halfEdge *e)
{return adj_pool + ((e - adj_pool) ^ 1);
}int dfsCnt;
int dfn[MaxN + 1];
halfEdge *faE[MaxN + 1], *moE[MaxN + 1];void dfs(int v)
{dfn[v] = ++dfsCnt;for (halfEdge *e = adj[v]; e; e = e->next)if (e != faE[v] && e != moE[v]){if (!dfn[e->u]){faE[e->u] = oppoE(e), moE[e->u] = NULL;dfs(e->u);}else if (dfn[e->u] < dfn[v]){assert(moE[v] == NULL);int u = v;halfEdge *lastE = e;while (u != e->u){moE[u] = lastE;lastE = oppoE(faE[u]);u = faE[u]->u;}}}
}#define cactus_children_for_each(v, e) for (halfEdge *e = adj[v]; e; e = e->next) if (e != faE[v] && e != moE[v] && faE[e->u] == oppoE(e))
#define cir_for_each(v, vu) for (int vu = v; vu != faE[v]->u; vu = moE[vu]->u)inline int cir_beg(int v)
{int u = v;while (oppoE(faE[u]) == moE[faE[u]->u])u = faE[u]->u;return u;
}
inline int cir_end(int v)
{int u = v;while (oppoE(moE[u]) == faE[moE[u]->u])u = moE[u]->u;return u;
}
inline int cir_len(int v)
{int l = 1;cir_for_each(v, u)l++;return l;
}int up_root[MaxN + 1];
int up_vC[MaxN + 1];
int up_vS[MaxN + 1];
int up_inh_l[MaxN + 1];
polyarray up_poly[MaxN + 1];polyarray calc(int root, int last_vC);polyarray calc_under(int v, int last_vC)
{polyarray under;if (moE[v]){recoverEdgeType r1 = delEdge(v, faE[v]->u);recoverEdgeType r2 = delEdge(v, moE[v]->u);faE[v] = moE[v] = NULL;under = calc(v, last_vC);faE[v] = r1.second;moE[v] = r2.second;recoverEdge(r2);recoverEdge(r1);}else{recoverEdgeType r = delEdge(v, faE[v]->u);faE[v] = NULL;under = calc(v, last_vC);faE[v] = r.second;recoverEdge(r);}return under;
}
polyarray calc_children_under(int v, int last_vC)
{polyarray under;under.resize(1);under.a[0] = 1;cactus_children_for_each(v, e){if (moE[e->u]){int totL = cir_len(e->u);int curL = 1;cir_for_each(e->u, u){polyarray cur = calc_under(u, last_vC);polyaddto(under, cur, curL);polyaddto(under, cur, totL - curL);curL++;}}else{polyarray cur = calc_under(e->u, last_vC);polyaddto(under, cur, 1);}}return under;
}polyarray calc(int root, int last_vC)
{int q_n = 0;static int q[MaxN];q[q_n++] = root;for (int i = 0; i < q_n; i++){int v = q[i];cactus_children_for_each(v, e){if (moE[e->u]){cir_for_each(e->u, u)q[q_n++] = u;}elseq[q_n++] = e->u;}}int vC = -1, wC = INT_MAX;static int wei[MaxN + 1];for (int i = q_n - 1; i >= 0; i--){int v = q[i];int wV = 0;wei[v] = 1;cactus_children_for_each(v, e){if (moE[e->u]){int curW = 0;int vU = -1, wU = 0;cir_for_each(e->u, u){curW += wei[u];if (vU == -1 || wei[u] > wei[vU])vU = u;}wei[v] += curW;relax(wV, wei[vU]);relax(wU, q_n - curW);cir_for_each(e->u, u){if (e->u != vU)relax(wU, wei[e->u]);}cactus_children_for_each(vU, eu){if (moE[eu->u]){cir_for_each(eu->u, k)relax(wU, wei[k]);}elserelax(wU, wei[eu->u]);}if (wU < wC)vC = vU, wC = wU;}else{wei[v] += wei[e->u];relax(wV, wei[e->u]);}}relax(wV, q_n - wei[v]);if (!moE[v]){if (wV < wC)vC = v, wC = wV;}}int vB = moE[vC] ? cir_beg(vC) : vC;polyarray res;if (moE[vC]){recoverEdgeType r1 = delEdge(faE[vB]->u, cir_end(vB));recoverEdgeType r2 = delEdge(faE[vB]->u, vB);res = calc(root, last_vC);recoverEdge(r2);recoverEdge(r1);}else if (vC != root){recoverEdgeType r = delEdge(faE[vC]->u, vC);res = calc(root, last_vC);recoverEdge(r);}int inh_l = 0;polyarray above;above.resize(1);above.a[0] = 1;int vS = moE[vC] ? faE[vB]->u : vC;if (vS != root){int vT = vS;if (vT == vC){inh_l++;vT = faE[vT]->u;}for (int v = vT, p = vT; p != root; v = up_vC[v]){while (p != up_vS[v]){if (moE[p]){int s = faE[cir_beg(p)]->u;int le_l = 0, ri_l = 0;for (int u = p; u != s; u = faE[u]->u)le_l++;for (int u = p; u != s; u = moE[u]->u)ri_l++;inh_l += min(le_l, ri_l);polyaddto(above, above, abs(le_l - ri_l));p = s;}else{inh_l++;p = faE[p]->u;}}inh_l += up_inh_l[v];polymulto(above, up_poly[v]);p = up_root[v];}}up_root[vC] = root;up_vS[vC] = vS;up_vC[vC] = last_vC;up_inh_l[vC] = inh_l;up_poly[vC] = above;polyarray under;if (moE[vB]){int totL = cir_len(vB);int curL = 1;cir_for_each(vB, u){polyarray cur = u != vC ? calc_under(u, vC) : calc_children_under(u, vC);polyaddto(under, cur, curL);polyaddto(under, cur, totL - curL);curL++;}}else{under = calc_children_under(vC, vC);up_inh_l[vC] = inh_l;up_poly[vC] = above;}polymulto(under, above);polyaddto(res, under, inh_l);return res;
}int main()
{cin >> n >> m;for (int i = 0; i < m; i++){int v, u;scanf("%d %d", &v, &u);addEdge(v, u), addEdge(u, v);}dfsCnt = 0;faE[1] = moE[1] = NULL;dfs(1);polyarray res = calc(1, 0);int res_d = res.getDegree();for (int k = 1; k < n; k++)printf("%d\n", k <= res_d ? res.a[k] : 0);return 0;
}

动态仙人掌

介绍

动态仙人掌问题
一类在仙人掌上动态维护信息的问题,动态维护包含修改形态和修改相关信息两种。

没错,最后我们要讲的就是我们前一段学的 Link-Cut Tree 在仙人掌上的迁移: Link-Cut Cactus.

结构

那么首先面临的一个难题是,什么是实边,什么是虚边?
我们分三种情况进行讨论:

树上的情况

LCT 维护的是熟的一条链,于是我们可以维护仙人掌的一条链。

没有环的话,就跟 LCT 一样,每个结点有一个偏爱孩子,用实边连起来,其它儿子用虚边连起来。

环上的情况

对于一个环,我们定义它的父亲为环上离仙人掌的根最近的节点,记为 A ,我们定义换上单的偏爱孩子为环上最后一次 access 到的节点,记为 B 。

那么根据定义,我们将 A 和 B 的最短路用实边连起来。

对于环上的其他结点构成的链,也用实边连起来,我们称这条链为\textbf{额外链}。

特殊情况

如前图中的二逼情况所示,如果我们 access 一个环的跟的时候,就会出现这种情况,我们需要对此加个特判。

注意:
以下三种情况也是不被允许的:

环上信息的处理

为了更为方便地操作环,我们需要对环上的信息进行处理。

  • 记录 pAp_A 为环的根, pBp_B 为环的偏爱儿子, pExp_{Ex} 是环的额外链的辅助树的根节点。
  • 而环上的两条黑边也会作为额外链的 firstEfirstE 和 lastElastE 存下来。

需要注意的是:在没有额外链的情况下,会出现有一条黑边没存的情况。


我们只需要开个 missingEmissingE 来记录这条边即可。

换根操作

我们在仙人掌上换根是不能直接套用树上的方法,因为这样会导致额外链以及 pA, pBpA,\ p_B 的方向不正确。

不过问题是可解的,我们对于 pA, pBpA,\ p_B 可以比较其在辅助树中的先后顺序,如果反了就将环信息中的 pA, pBpA,\ p_B 指针对调。

之后的步骤就显而易见了:

实现

伪代码 by Vfleaking

void access(x)
{for (p = x, q = NULL; p; q = p, p = p->fa){splay(p);if (p->prevE && p->prevE->cir) // 判断p是否在环上。注意环的根不算作在这个环上。{isTogether = false; // 判断是否是2B情况。cir = p->prevE->cir; // 获取p->prevE所在的环的信息// 由于p可能在额外链上而之前很狗血地splay了,会导致记录的pEx不正确。if (cir->pEx && !cir->pEx->isRoot()) cir->pEx = p;splay(cir->pB);splay(cir->pA);if (cir->pB->isRoot()) // 2B情况{if (cir->pB->fa != cir->pA) // 如果pA、pB顺序不对则进行调整{swap(cir->pA, cir->pB);if (cir->pEx)cir->pEx->tag_rev(); // 打上翻转标记}}else // 文艺情况{isTogether = true;splay_until(cir->pB, cir->pA); // 把pB splay到pA下面if (cir->pA->lc == cir->pB) // 如果pA、pB顺序不对则进行调整{rotate(cir->pB); // 一次旋转把pB转成根swap(cir->pA, cir->pB);if (cir->pEx)cir->pEx->tag_rev(); // 打上翻转标记}cir->pA->rc = NULL;cir->pA->nextE = NULL; // 暂时断开pA与下面部分的链接转化为2B情况}cir->pB->rc = cir->pEx;// pEx为空的情况,用missingE补上cir->pB->nextE = cir->pEx ? cir->pEx->msg.firstE : cir->missingE;if (cir->pEx)cir->pEx->fa = cir->pB;// 这样环就被整个地接了起来成为了一棵splay。p->splay();// 比较哪边走比较短,如果不是往左走短就调整一下if (p->getLcTotL() > p->getRcTotL()){p->tag_rev();p->tag_down();}cir->pB = p;cir->pEx = p->rc; // 把较长的那条变为额外链cir->missingE = p->rc ? NULL : p->nextE; // pEx为空的情况,用missingE补上if (cir->pEx)cir->pEx->fa = NULL;p->rc = q;p->nextE = q ? q->msg.firstE : NULL;p->update();if (isTogether) // 如果是文艺情况还得把pA接回来{cir->pA->rc = p;cir->pA->nextE = p->msg.firstE;p->splay();}}else // 普通情况{p->rc = q;p->nextE = q ? q->msg.firstE : NULL;p->update();}}
}

[UOJ65]动态仙人掌 III

标程 by Vfleaking

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cassert>
#include <climits>
using namespace std;const int INF = INT_MAX;const int MaxN = 100000;inline int getint()
{char c;while (c = getchar(), ('0' > c || c > '9') && c != '-');if (c != '-'){int res = c - '0';while (c = getchar(), '0' <= c && c <= '9')res = res * 10 + c - '0';return res;}else{int res = 0;while (c = getchar(), '0' <= c && c <= '9')res = res * 10 + c - '0';return -res;}
}template <class T>
class BlockAllocator
{
private:static const int BlockL = 10000;union TItem{char rt[sizeof(T)];TItem *next;};TItem *pool, *tail;TItem *unused;
public:BlockAllocator(){pool = NULL;unused = NULL;}T *allocate(){TItem *p;if (unused){p = unused;unused = unused->next;}else{if (pool == NULL)pool = new TItem[BlockL], tail = pool;p = tail++;if (tail == pool + BlockL)pool = NULL;}return (T*)p;}void deallocate(T *pt){TItem *p = (TItem*)pt;p->next = unused, unused = p;}
};struct edgeWeight;
struct path_message;
struct lcc_circle;
struct lcc_edge;
struct lcc_message;
struct lcc_node;struct edgeWeight
{int wA, wB;edgeWeight(){}edgeWeight(const int &_wA, const int &_wB): wA(_wA), wB(_wB){}friend inline bool operator==(const edgeWeight &lhs, const edgeWeight &rhs){return lhs.wA == rhs.wA && lhs.wB == rhs.wB;}friend inline bool operator!=(const edgeWeight &lhs, const edgeWeight &rhs){return lhs.wA != rhs.wA || lhs.wB != rhs.wB;}
};struct path_message
{int minLA;int minWB;path_message(){}path_message(const edgeWeight &ew): minLA(ew.wA), minWB(ew.wB){}path_message(const int &_minLA, const int &_minWB): minLA(_minLA), minWB(_minWB){}void setEmpty(){minLA = 0;minWB = INF;}void setInvalid(){minLA = -1;minWB = -1;}bool valid() const{return minLA != -1;}void setMultiple(){minWB = -1;}friend inline path_message operator+(const path_message &lhs, const path_message &rhs){if (lhs.minLA < rhs.minLA)return lhs;else if (rhs.minLA < lhs.minLA)return rhs;elsereturn path_message(lhs.minLA, -1);}friend inline path_message operator*(const path_message &lhs, const path_message &rhs){return path_message(lhs.minLA + rhs.minLA, min(lhs.minWB, rhs.minWB));}
};struct lcc_circle
{lcc_node *pA, *pB;lcc_node *pEx;lcc_edge *missingE;bool equalL;
};
struct lcc_edge
{edgeWeight ew;lcc_circle *cir;inline lcc_circle *getCir(){return this ? this->cir : NULL;}
};
struct lcc_message
{path_message pathMsg;lcc_edge *firstE, *lastE;bool hasCir;bool hasMultiplePath;void rev(){swap(firstE, lastE);}void coverCir(lcc_circle *cir, bool isSingle){hasCir = !isSingle && cir != NULL;hasMultiplePath = false;if (cir && firstE->getCir() != cir && lastE->getCir() != cir){if (cir->equalL)hasMultiplePath = true;}}void addWB(int delta, bool isSingle){if (!isSingle)pathMsg.minWB += delta;}friend inline lcc_message operator+(const lcc_message &lhs, const lcc_message &rhs){lcc_message res;assert(lhs.lastE == rhs.firstE);lcc_edge *e = lhs.lastE;res.pathMsg = lhs.pathMsg * path_message(e->ew) * rhs.pathMsg;res.hasMultiplePath = lhs.hasMultiplePath || rhs.hasMultiplePath;if (e->cir && lhs.firstE->getCir() != e->cir && rhs.lastE->getCir() != e->cir){if (e->cir->equalL)res.hasMultiplePath = true;}res.firstE = lhs.firstE, res.lastE = rhs.lastE;res.hasCir = lhs.hasCir || e->cir || rhs.hasCir;return res;}
};
struct lcc_node
{lcc_node *fa, *lc, *rc;lcc_edge *prevE, *nextE;lcc_message msg;bool hasRev;bool hasCoveredCir;lcc_circle *coveredCir;int wBDelta;bool isRoot(){return !fa || (fa->lc != this && fa->rc != this);}void rotate(){lcc_node *x = this, *y = x->fa, *z = y->fa;lcc_node *b = x == y->lc ? x->rc : x->lc;x->fa = z, y->fa = x;if (b)b->fa = y;if (z){if (z->lc == y)z->lc = x;else if (z->rc == y)z->rc = x;}if (y->lc == x)x->rc = y, y->lc = b;elsex->lc = y, y->rc = b;y->update();}void allFaTagDown(){int anc_n = 0;static lcc_node *anc[MaxN];anc[anc_n++] = this;for (int i = 0; !anc[i]->isRoot(); i++)anc[anc_n++] = anc[i]->fa;for (int i = anc_n - 1; i >= 0; i--)anc[i]->tag_down();}void splay(){allFaTagDown();while (!this->isRoot()){if (!fa->isRoot()){if ((fa->lc == this) == (fa->fa->lc == fa))fa->rotate();elsethis->rotate();}this->rotate();}this->update();}void splay_until(lcc_node *target){allFaTagDown();while (this->fa != target){if (fa->fa != target){if ((fa->lc == this) == (fa->fa->lc == fa))fa->rotate();elsethis->rotate();}this->rotate();}this->update();}int getLcTotL(){if (!prevE)return 0;int totL = prevE->ew.wA;if (lc)totL += lc->msg.pathMsg.minLA + msg.firstE->ew.wA;return totL;}int getRcTotL(){if (!nextE)return 0;int totL = nextE->ew.wA;if (rc)totL += rc->msg.pathMsg.minLA + msg.lastE->ew.wA;return totL;}void access(){for (lcc_node *p = this, *q = NULL; p; q = p, p = p->fa){p->splay();if (p->prevE && p->prevE->cir){bool isTogether = false;lcc_circle *cir = p->prevE->cir;if (cir->pEx && !cir->pEx->isRoot())cir->pEx = p;cir->pB->splay(), cir->pA->splay();if (cir->pB->isRoot()){if (cir->pB->fa != cir->pA){swap(cir->pA, cir->pB);if (cir->pEx)cir->pEx->tag_rev();}}else{isTogether = true;cir->pB->splay_until(cir->pA);if (cir->pA->lc == cir->pB){cir->pB->rotate();swap(cir->pA, cir->pB);if (cir->pEx)cir->pEx->tag_rev();}cir->pA->rc = NULL, cir->pA->nextE = NULL;}cir->pB->rc = cir->pEx, cir->pB->nextE = cir->pEx ? cir->pEx->msg.firstE : cir->missingE;if (cir->pEx)cir->pEx->fa = cir->pB;p->splay();if (p->getLcTotL() > p->getRcTotL())p->tag_rev(), p->tag_down();cir->pB = p;cir->pEx = p->rc, cir->missingE = p->rc ? NULL : p->nextE;cir->equalL = p->getLcTotL() == p->getRcTotL();if (cir->pEx)cir->pEx->fa = NULL;p->rc = q, p->nextE = q ? q->msg.firstE : NULL;p->update();if (isTogether){cir->pA->rc = p, cir->pA->nextE = p->msg.firstE;p->splay();}}else{p->rc = q, p->nextE = q ? q->msg.firstE : NULL;p->update();}}this->splay();}void makeRoot(){this->access();this->tag_rev(), this->tag_down();}lcc_node *findRoot(){lcc_node *p = this;p->access();while (p->tag_down(), p->lc)p = p->lc;p->splay();return p;}void tag_rev(){hasRev = !hasRev;msg.rev();}void tag_coverCir(lcc_circle *cir){hasCoveredCir = true;coveredCir = cir;msg.coverCir(cir, !lc && !rc);}void tag_addWB(int delta){wBDelta += delta;msg.addWB(delta, !lc && !rc);}void tag_down(){if (hasRev){swap(lc, rc);swap(prevE, nextE);if (lc)lc->tag_rev();if (rc)rc->tag_rev();hasRev = false;}if (hasCoveredCir){if (lc){prevE->cir = coveredCir;lc->tag_coverCir(coveredCir);}if (rc){nextE->cir = coveredCir;rc->tag_coverCir(coveredCir);}hasCoveredCir = false;}if (wBDelta != 0){if (lc){prevE->ew.wB += wBDelta;lc->tag_addWB(wBDelta);}if (rc){nextE->ew.wB += wBDelta;rc->tag_addWB(wBDelta);}wBDelta = 0;}}void update(){msg.pathMsg.setEmpty();msg.firstE = prevE, msg.lastE = nextE;msg.hasCir = false;msg.hasMultiplePath = false;if (lc)msg = lc->msg + msg;if (rc)msg = msg + rc->msg;}
};int n;
lcc_node lccVer[MaxN + 1];
BlockAllocator<lcc_edge> lccEAllocator;
BlockAllocator<lcc_circle> lccCirAllocator;void cactus_init()
{for (int v = 1; v <= n; v++){lcc_node *x = lccVer + v;x->fa = x->lc = x->rc = NULL;x->prevE = x->nextE = NULL;x->hasRev = false;x->hasCoveredCir = false;x->wBDelta = 0;x->update();}
}bool cactus_link(int v, int u, int wA, int wB)
{if (v == u)return false;edgeWeight ew(wA, wB);lcc_node *x = lccVer + v, *y = lccVer + u;x->makeRoot(), y->makeRoot();if (x->fa){x->access();if (x->msg.hasCir)return false;lcc_circle *cir = lccCirAllocator.allocate();lcc_edge *e = lccEAllocator.allocate();e->ew = ew, e->cir = cir;cir->pA = y, cir->pB = x, cir->pEx = NULL;cir->missingE = e;x->tag_coverCir(cir);x->access();}else{lcc_edge *e = lccEAllocator.allocate();e->ew = ew, e->cir = NULL;x->fa = y, x->prevE = e, x->update();}return true;
}
bool cactus_cut(int v, int u, int wA, int wB)
{if (v == u)return false;edgeWeight ew(wA, wB);lcc_node *x = lccVer + v, *y = lccVer + u;if (x->findRoot() != y->findRoot())return false;y->makeRoot(), x->access();y->splay_until(x);lcc_circle *cir = x->prevE->cir;if (cir && cir->pA == y && !cir->pEx && cir->missingE->ew == ew){lcc_edge *e = cir->missingE;x->tag_coverCir(NULL);lccCirAllocator.deallocate(cir);lccEAllocator.deallocate(e);return true;}if (!y->rc && x->prevE->ew == ew){lcc_edge *e = x->prevE;lccEAllocator.deallocate(e);if (cir){if (cir->pEx){cir->pEx->tag_rev();cir->pEx->fa = y, y->rc = cir->pEx;y->nextE = cir->pEx->msg.firstE;x->prevE = cir->pEx->msg.lastE;}elsey->nextE = x->prevE = cir->missingE;y->update(), x->update();x->tag_coverCir(NULL);lccCirAllocator.deallocate(cir);}else{y->fa = NULL, y->nextE = NULL, y->update();x->lc = NULL, x->prevE = NULL, x->update();}return true;}return false;
}
bool cactus_add(int qv, int qu, int delta)
{lcc_node *x = lccVer + qv, *y = lccVer + qu;if (x->findRoot() != y->findRoot())return false;x->makeRoot(), y->access();if (y->msg.hasMultiplePath)return false;y->tag_addWB(delta);return true;
}
path_message cactus_query(int qv, int qu)
{path_message res;lcc_node *x = lccVer + qv, *y = lccVer + qu;if (x->findRoot() != y->findRoot()){res.setInvalid();return res;}x->makeRoot(), y->access();res = y->msg.pathMsg;if (y->msg.hasMultiplePath)res.setMultiple();return res;
}int main()
{int nQ;cin >> n >> nQ;cactus_init();while (nQ--){char type;while (type = getchar(), type != 'l' && type != 'c' && type != 'a' && type != 'd');if (type == 'l'){int v = getint(), u = getint(), wA = getint(), wB = getint();if (cactus_link(v, u, wA, wB))printf("ok\n");elseprintf("failed\n");}else if (type == 'c'){int v = getint(), u = getint(), wA = getint(), wB = getint();if (cactus_cut(v, u, wA, wB))printf("ok\n");elseprintf("failed\n");}else if (type == 'a'){int v = getint(), u = getint(), delta = getint();if (cactus_add(v, u, delta))printf("ok\n");elseprintf("failed\n");}else if (type == 'd'){int v = getint(), u = getint();path_message ret = cactus_query(v, u);printf("%d %d\n", ret.minLA, ret.minWB);}else{puts("error!");}}return 0;
}

广义圆方树

经过前面各类仙人掌题目的讲解,大家恐怕已经对仙人掌产生了恐惧,而事实上,有难度的仙人掌题在近几年也只是在国家集训队水平的比赛里才会出现。

不过,这不是说仙人掌对国集水平以下的选手意义不大:

  • 首先,仙人掌暴力 DP 问题难度并不大,在省选、 NOI 甚至 NOIP 中可能出现;

  • 其次,仙人掌问题的处理能很好锻炼选手的特殊情况全面考虑并正确处理的能力,提升对超长代码的把握;
    我们在点双题目中,如果对每个点双建立方点,然后对点双中的每个点连边,这样也可以构造出一种新的圆方树,我们称之为广义圆方树

根据圆方树和仙人掌等价性定理,我们可以为一张一般的图建立一棵等效仙人掌:一个环代表一个点双。

这样的一种思想可能在思路枯竭时解救我们。

Tourists

据说,这是一道 NOIP 难度的仙人掌题。

题目

给出一张带点权图,每次询问两点之间的简单路径中,权值最小的权值是多少。点数 NN 、边数 MM 、询问数 QQ 均 ≤106\leq10^6

由于路径太多,直接考虑点双不方便,我们考虑建立圆方树、转化为等效仙人掌;

这样问题就转化为:两点所有简单路径的并集中权值的最小值;

只需将每个方点上的权值设为其连出所有圆点的最小值,这样问题就转化为链上的最小值。

之后只需并查集求 LCA 时顺便求一下最小值即可,本题是 NOIP 难度得证。

参考文献

[1] immortalCO. Making Graph into Trees[R]. 绍兴:中国计算机学会, 2017.
[2] Gintoki. 浅谈仙人掌问题[R]. 绍兴:中国计算机学会, 2017.
[3] 王逸松. 仙人掌相关算法及其应用[R]. 杭州:中国计算机学会, 2015.

仙人掌相关问题的处理方法(未完待续)相关推荐

  1. 线下实体店铺会员引流的四种方法-未完待续

    2022年,因为疫情的影响以及电商平台的持续发力,线下门店消费者到店流量明显减少,线下实体门店如何才能通过更低的成本更高的效率进行线下引流呢?这是当下零售企业需要重视的问题. 今天博阳为您介绍4种会员 ...

  2. Python-面向对象-魔法方法(未完待续)

    目录 一.属性相关的魔法方法 (1) __getattribute__ (2)__getattr__ (3)__setattr__ (4)__delattr__ 二,容器魔法方法 (1)__len__ ...

  3. 游戏思考26:游戏服务器压力测试文档(新增linux相关命令,02/10未完待续)

    文章目录 一.压力测试关注点 二.计算最耗时的加载操作 1)从数据库读取数据,对加载的类型进一步划分各种类型,计算最耗时操作 2)查看CPU随着在线人数的变化所占百分比 3)查看内存变化 4)备注 三 ...

  4. 观察多个线程同时运行|| 查看进程线程的方法——未完待续

    观察多个线程同时运行:主要是理解,交替执行,谁先谁后,不由我们控制 查看进程线程的方法

  5. 《图解 HTTP》读书笔记(未完待续)

    ARP 协议(Address Resolution Protocol)一种以解析地址的协议,根据通信双方的 IP 地址就可以查出对应的 MAC 地址. MAC( Media Access Contro ...

  6. pythonb超分辨成像_Papers | 超分辨 + 深度学习(未完待续)

    1. SRCNN 1.1. Contribution end-to-end深度学习应用在超分辨领域的开山之作(非 end-to-end 见 Story.3 ). 指出了超分辨方向上传统方法( spar ...

  7. 性能测试调优篇---未完待续

    性能测试调优一: 1.首先,看下选测交易的整个走向 纯系统内部交易: 选测交易如果是系统内的交易,每一步请求都和系统交互几次,访问了几个数据库,访问了数据库的那几张表?? 该交易走了那几台机器,这几台 ...

  8. 省钱版----查找 IoT 设备TTL线序__未完待续

    作者:仙果 原文来自:省钱版--查找 IoT 设备TTL线序 省钱版----查找 IoT 设备TTL线序__未完待续 缘由 在IoT固件调试分析的过程中,建议首先在IoT设备的板子上焊接调试线,这是能 ...

  9. Diabetes 糖尿病及其并发症.|2021/1/25(未完待续)

    目录 前言: 正文: ①宏观严重性: ②普遍状况:" 三多一少": ③定义 : ③胰岛素(Insulin,一种激素(harmone)) ④引出糖尿病病理: 1> 1-型糖尿病 ...

  10. 构建Linux根文件系统(未完待续)

          所谓制作根文件系统, 就是创建各种目录, 并且在里面创建各种文件. 比如在/bin ./sbin 目录下存放各种可执行程序, 在/etc 目录下存放配置文件, 在/lib 目录下存放库文件 ...

最新文章

  1. 我读过的最好的epoll讲解--转自”知乎“ 【转】
  2. 【图像处理】直方图均衡化
  3. SDWebImage 加载Https自签名证书时的图片问题
  4. Kotlin的高阶函数和常用高阶函数
  5. Codeigniter 4.0-dev 版源码学习笔记之六——控制器
  6. UNIX(多线程):21---线程池实现原理
  7. 更改Web.config中对上传文件大小限制
  8. 苹果:2020全年App Store阻止了超15亿美元潜在诈骗交易
  9. 6个原则、50条秘技提高HTML5应用及网站性能
  10. VDN元宇宙游戏公会|Cool Metaverse首个开放共享式元宇宙平台
  11. 1284. Minimum Number of Flips to Convert Binary Matrix to Zero Matrix
  12. 后台管理系统模板,简易模板
  13. 为什么TIME_WAIT状态是2MSL?(2个原因)
  14. Clang vs Other Open Source Compilers
  15. Flutter 仿滴滴出行App,2021最新华为Android校招面试题
  16. C++实现与电脑进行石头剪刀布的游戏
  17. Luat模块应用手册-指南-Luat二次开发教程指南-功能开发教程-虚拟AT
  18. Ubuntu 16.04 一系列软件安装命令,包括QQ、搜狗、Chrome、vlc、网易云音乐安装方法(转载)...
  19. Lecture 13: Bernoulli Process
  20. 直播APP搭建好之后,该如何运营呢

热门文章

  1. 数字体育进入黄金窗口期
  2. 又是一个想当然的错误
  3. 从端到云——工业物联网项目全栈快速开发实践
  4. vue在移动端实现禁用物理返回键
  5. 安科瑞DDS/DTS/DTZ系列计量表,具有体积小巧、精度高、可靠性好、安装方便等优点
  6. java输入语句怎么写_java输入语句应该怎样写?示例演示
  7. 雷达干扰技术(二)数字干扰合成及相关技术
  8. 舒尔特方格 (Schulte Grid)
  9. 我的河海大学计算机考研经验之谈
  10. cannot find -l****问题的解决办法