【洛谷6966】[NEERC2016] Cactus Construction(圆方树+构造)
- 给定一棵\(n\)个点的仙人掌。
- 初始每个点自成一个点集,所有点颜色都为\(1\),你可以进行三种操作:合并两个点\(x,y\)所在的点集;在点集\(x\)中\(a,b\)两种颜色的点之间两两连边;把点集\(x\)中所有颜色为\(a\)的点的颜色修改为\(b\)。
- 要求构造出这棵仙人掌。
- \(n\le5\times10^4\)
圆方树
遇上仙人掌题目首先无脑构造一棵圆方树。
对于方点的构造
对于方点,我们的目的是把所有子节点按顺序连成一条链,并给两个端点标上与其他点不相同的颜色。
我们维护一条链\(1-2-2-...-2-3\),其中\(1\)表示链首,\(2\)表示中间不再连边的普通点,\(3\)表示链尾。
注意,子节点子树内除自己外的其他点同样不再连边,也应当标记成\(2\),否则会对连边造成干扰。
新加入的点我们先标记成\(4\),合并点集后在\(3,4\)之间连边,然后把\(3\)修改成\(2\),把\(4\)修改成\(3\)。
最后我们把\(3\)修改成\(1\),就满足除了链首和链尾为\(1\)以外,其余所有点都是\(2\)。
对于圆点的构造
对于圆点,我们的目的是把所有子节点的对应链都接到当前点上。
我们先合并所有子节点的点集,由于链首和链尾颜色都是\(1\),我们先把\(1\)修改成\(4\),然后与当前点合并,在\(1,4\)之间连边就完成了连边任务。
最后我们把\(4\)修改成\(2\),就满足除了当前点为\(1\)以外,子树内其余点都是\(2\)。
代码:\(O(m)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define M 2000000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,tot,ee,lnk[N+5];struct edge {int to,nxt;}e[2*M+5];
namespace FastIO
{
#define FS 20000000
#define tc() (*FA++)
#define pc(c) (*FC++=c)
int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
I void Input() {FB=(FA=FI)+fread(FI,1,FS,stdin);}
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void write(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);}
I void OP_j(CI x,CI y) {++tot,pc('j'),pc(' '),write(x),pc(' '),write(y),pc('\n');}
I void OP_r(CI x,CI y,CI z) {++tot,pc('r'),pc(' '),write(x),pc(' '),write(y),pc(' '),write(z),pc('\n');}
I void OP_c(CI x,CI y,CI z) {++tot,pc('c'),pc(' '),write(x),pc(' '),write(y),pc(' '),write(z),pc('\n');}
I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
}using namespace FastIO;
namespace RST//圆方树
{
#define nadd(x,y) (ne[++nee].nxt=nlnk[x],ne[nlnk[x]=nee].to=y)
int ct,d,dfn[N+5],low[N+5],f[N+5],nee,nlnk[2*N+5];edge ne[4*N+5];
I void Build(CI x,RI y) {++ct;W(nadd(ct,y),nadd(y,ct),y^x) y=f[y];}//新建一个方点
I void Tarjan(CI x) {dfn[x]=low[x]=++d;for(RI i=lnk[x],y,t;i;i=e[i].nxt) (y=e[i].to)^f[x]&&//建树
((t=dfn[y]?dfn[y]:(f[y]=x,Tarjan(y),low[y]))>dfn[x]?(Build(x,y),0):low[x]=min(low[x],t));}//判断是否产生环
int s[N+5],id[2*N+5];I void dfs(CI x,CI lst=0)//遍历一遍
{
RI i,t=0;for(i=nlnk[x];i;i=ne[i].nxt) ne[i].to^lst&&(dfs(ne[i].to,x),0);
for(i=nlnk[x];i;i=ne[i].nxt) ne[i].to^lst&&(s[++t]=ne[i].to);//按顺序存下子节点
if(x>n)//对于方点
{
if(id[x]=s[t],t==1) return;//只有一个子节点时无需操作
for(i=2;i<=t;++i) OP_r(s[i],1,4),OP_j(s[i-1],s[i]),//把要加入的点标记为4,然后合并
i^2?(OP_c(s[i],3,4),OP_r(s[i],3,2)):OP_c(s[i],1,4),OP_r(s[i],4,3);//连边然后将原本的3改为2(特殊处理与链首连边),把4改为3
return OP_r(s[t],3,1);//把3改为1,使得除链首和链尾为1外其余都是2
}
if(!t) return;for(i=2;i<=t;++i) OP_j(id[s[i-1]],id[s[i]]);//把所有子节点的点集合并
OP_r(id[s[1]],1,4),OP_j(x,id[s[1]]),OP_c(x,1,4),OP_r(x,4,2);//把子节点的1改为4,合并后在1,4间连边,然后把4修改为2,使得除当前点为1外其余都是2
}
}
int main()
{
RI i,x,y,z;for(Input(),read(n,m),i=1;i<=m;++i) {read(x,y);W(--x) read(z),add(y,z),add(z,y),y=z;}
return RST::ct=n,RST::Tarjan(1),RST::dfs(1),printf("%d\n",tot),clear(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒