圆方树

神奇的建树方法。

对每个点双新建一个方点,把这个点双里原来的边去掉,并把每个点连向这个方点。这样原图就是一棵树,而且这棵树有一些优美的性质:
1.原图的割点就是圆方树中度数大于 1 的圆点。
2.树上任意一条路径上圆点方点间隔分布。
3.如果圆点的 size 为 1 ,那么一个圆点子树的 size 和就是它“下面”的所有点的数量(用心灵去感受)。

如果把一个点双里深度最小的点称作这个点双的“根”,那么一般来讲为了不重复维护信息,每个方点维护所在点双里除了根的信息,然后对于dfs树的根单独处理。注意修改操作之类的要单独处理 1 号点,因为它不在任何一个方点里。
代码在 t a r j a n tarjan tarjan 的基础上稍作修改:

void tarjan(int u,int father)
{
	vis[u]=1,dfn[u]=low[u]=++tim,sta[++top]=u;
	for(int i=head1[u];i;i=ed1[i].next)
	{
		int v=ed1[i].to;
		if(v==father) continue;
		if(!dfn[v]) 
		{
			tarjan(v,u);
			if(low[v]>=dfn[u])
			{
				point++;
				add2(point,u),add2(u,point);
				int now=-1;
				do{
					now=sta[top--];
					bel[now]=point;
					add2(now,point),add2(point,now);
					sccsize[point-n]++;
				}while(now!=v);
			}
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v]) low[u]=min(low[u],dfn[v]);
	}
}

###1
题意:无向联通图,点有点权,每次询问两点间简单路径的并的第 k k k 大点权,带修改。

首先,简单路径是不重复经过点的路径。简单路径并的点也就是圆方树上两个点所属方点间路径上的点所在的点双的点的集合…

方点维护所在点双中除了根的圆点的信息。查询两点 u , v u,v u,v 的答案,就是树上 u , v u,v u,v 间的信息(只有方点维护信息),然后特判 l c a lca lca ,如果 l c a lca lca 是圆点,那么它一定是某个应在答案里的点双的根,应该被计算进答案。同理,如果 l c a lca lca 是方点,那么它的父亲一定是圆点,也应该被计算进答案。然后就是圆方树上树状数组套主席树啦。

#include
#include
#include
using namespace std;
struct edge{
	int to,next;
}ed1[400010],ed2[400010];
struct node{
	int x,y,k,opt;
}q[200010];
int lowbit(int x){
	return x&(-x);
}
int ls[20000010],rs[20000010],sum[20000010],root[200010],a[200010],b[200010],len[200010],f[200010][19],head1[200010],head2[200010],size1,size2,tim,bel[200010];
int sccsize[200010],tmp[2][200010],cnt[2],point,dfn[200010],low[200010],n,m,T,num,vis[200010],sta[200010],top,A[200010],B[200010],tot,deep[200010];

inline void add1(int from,int to)
{
	ed1[++size1].to=to;
	ed1[size1].next=head1[from];
	head1[from]=size1;
}
inline void add2(int from,int to)
{
	ed2[++size2].to=to;
	ed2[size2].next=head2[from];
	head2[from]=size2;
}
inline void input()
{
	scanf("%d%d%d",&n,&m,&T),num=n;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add1(u,v),add1(v,u);
	}
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
	for(int i=1;i<=T;i++) 
	{
		scanf("%d%d%d",&q[i].opt,&q[i].x,&q[i].y);
		if(q[i].opt==2) scanf("%d",&q[i].k);
		else b[++num]=q[i].y;
	}
	sort(b+1,b+num+1);
	num=unique(b+1,b+num+1)-b-1;
	for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+num+1,a[i])-b;
}
void tarjan(int u,int father)
{
	vis[u]=1,dfn[u]=low[u]=++tim,sta[++top]=u;
	for(int i=head1[u];i;i=ed1[i].next)
	{
		int v=ed1[i].to;
		if(v==father) continue;
		if(!dfn[v]) 
		{
			tarjan(v,u);
			if(low[v]>=dfn[u])
			{
				point++;
				add2(point,u),add2(u,point);
				int now=-1;
				do{
					now=sta[top--];
					bel[now]=point;
					add2(now,point),add2(point,now);
					sccsize[point-n]++;
				}while(now!=v);
			}
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v]) low[u]=min(low[u],dfn[v]);
	}
}
void dfs(int u)
{
	A[u]=++tot;
	if(u>n) len[u]+=sccsize[u-n];
	for(int i=head2[u];i;i=ed2[i].next)
	{
		int v=ed2[i].to;
		if(v==f[u][0]) continue;
		f[v][0]=u;
		deep[v]=deep[u]+1;
		len[v]+=len[u];
		dfs(v);
	}
	B[u]=tot;
}
void update(int &root,int l,int r,int x,int k)
{
	if(!root) root=++tot;
	sum[root]+=k;
	if(l==r) return;
	int mid=(l+r)>>1;
	if(x<=mid) update(ls[root],l,mid,x,k);
	else update(rs[root],mid+1,r,x,k);
}
inline void modify(int pos,int x,int k)
{
	for(int i=pos;i<=point;i+=lowbit(i)) update(root[i],1,num,x,k);
}
inline void make_tree()
{
	point=n;
	tarjan(1,0);
	deep[1]=1;
	dfs(1);tot=0;
	for(int j=1;j<=18;j++)
		for(int i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1];
	for(int i=2;i<=n;i++)
    {
        modify(A[bel[i]],a[i],1);
        modify(B[bel[i]]+1,a[i],-1);
    }
}
inline int Lca(int x,int y)
{
	if(deep[x]=0;j--)
		if(deep[f[x][j]]>=deep[y]) x=f[x][j];
	if(x==y) return x;
	for(int j=18;j>=0;j--)
		if(f[x][j]!=f[y][j]) x=f[x][j],y=f[y][j];
	return f[x][0];
}
int query(int l,int r,int k,int w)
{
	if(l==r) return l;
	int mid=(l+r)>>1;
	int zz=0;
	for(int i=1;i<=cnt[0];i++) zz+=sum[ls[tmp[0][i]]];
	for(int i=1;i<=cnt[1];i++) zz-=sum[ls[tmp[1][i]]];
	if(w>=l&&w<=mid) zz++;
	if(k<=zz)
	{
		for(int i=1;i<=cnt[0];i++) tmp[0][i]=ls[tmp[0][i]];
		for(int i=1;i<=cnt[1];i++) tmp[1][i]=ls[tmp[1][i]];
		return query(l,mid,k,w);
	}
	for(int i=1;i<=cnt[0];i++) tmp[0][i]=rs[tmp[0][i]];
	for(int i=1;i<=cnt[1];i++) tmp[1][i]=rs[tmp[1][i]];
	return query(mid+1,r,k-zz,w);
}
inline void solve()
{
	for(int i=1;i<=T;i++)
	{
		int opt=q[i].opt,x=q[i].x,y=q[i].y,k=q[i].k;
		if(opt==1)
		{
			if(x==1)
			{
				a[x]=lower_bound(b+1,b+num+1,y)-b;
				continue;
			}
			modify(A[bel[x]],a[x],-1),modify(B[bel[x]]+1,a[x],1);
			a[x]=lower_bound(b+1,b+num+1,y)-b;
			modify(A[bel[x]],a[x],1),modify(B[bel[x]]+1,a[x],-1);
		}
		else
		{
			int lca=Lca(x,y),ff=f[lca][0];
			int zz=len[x]+len[y]-len[lca]*2+1;
			if(lca>n) zz+=sccsize[lca-n];
			if(k>zz)
			{
				puts("-1");
				continue;
			}
			if(lca>n) zz=a[ff];
			else zz=a[lca];
			cnt[0]=cnt[1]=0;
			for(int i=A[x];i;i-=lowbit(i)) tmp[0][++cnt[0]]=root[i];
			for(int i=A[y];i;i-=lowbit(i)) tmp[0][++cnt[0]]=root[i];
			for(int i=A[lca];i;i-=lowbit(i)) tmp[1][++cnt[1]]=root[i];
			for(int i=A[ff];i;i-=lowbit(i)) tmp[1][++cnt[1]]=root[i];
			printf("%d\n",b[query(1,num,k,zz)]);
		}
	}
}
int main()
{
	input();
	make_tree();
	solve();
	return 0;
}

###2
【APIO 2018】铁人两项
题意: n n n 个点的有向图,求有多少有序三元组 ( x , y , z ) (x,y,z) (x,y,z) ,使得存在一条 x − > y − > z x->y->z x>y>z 的简单路径。

思路是在圆方树上枚举所有圆点作为中间点,然后用全部方案减去不合法的。
什么样的路径是不合法的?当且仅当起点和终点在一个点双里,中间点不在这个点双里。

#include
#include
#define ll long long
using namespace std;
struct edge{
    int to,next;
}ed1[400010],ed2[400010];
ll ans,sum[400010];
int fa[400010],dfn[400010],low[400010],st[400010],cnt,sz1,head1[400010],tim,size[400010],top,point,n,m,root,head2[400010],sz2;
inline void add1(int from,int to)
{
    ed1[++sz1].to=to;
    ed1[sz1].next=head1[from];
    head1[from]=sz1;
}
inline void add2(int from,int to)
{
    ed2[++sz2].to=to;
    ed2[sz2].next=head2[from];
    head2[from]=sz2;
}
void tarjan(int u,int fa)
{
    dfn[u]=low[u]=++tim;
    st[++top]=u;
    for(int i=head1[u];i;i=ed1[i].next)
    {
        int v=ed1[i].to;
        if(v==fa) continue;
        if(!dfn[v])
        {
            tarjan(v,u);
            if(low[v]>=dfn[u])
            {
                point++;
                add2(point,u),add2(u,point);
                int now=-1;
                do{
                    now=st[top--];
                    add2(now,point),add2(point,now);
                }while(now!=v);
            }
            low[u]=min(low[u],low[v]);
        }
        else if(dfn[v]) low[u]=min(low[u],dfn[v]);
    }
}
void dfs1(int u)
{
    if(u<=n) size[u]=1;
    for(int i=head2[u];i;i=ed2[i].next)
    {
        int v=ed2[i].to;
        if(v==fa[u]) continue;
        fa[v]=u;
        dfs1(v);
        size[u]+=size[v];
        sum[u]+=1ll*size[v]*(size[v]-1);
    }
}
void dfs2(int u)
{
    for(int i=head2[u];i;i=ed2[i].next)
    {
        int v=ed2[i].to;
        if(v==fa[u]) continue;
        dfs2(v);
        if(u<=n) 
        {
            for(int j=head2[v];j;j=ed2[j].next)
            {
                int to=ed2[j].to;
                if(to==u) continue;
                ans-=1ll*size[to]*(size[to]-1);
            }
        }
    }
    if(u<=n)
    {
        ans+=1ll*(size[root]-1)*(size[root]-2);
        if(u!=root) 
        {
            ans-=1ll*(size[root]-size[fa[u]])*(size[root]-size[fa[u]]-1);
            ans-=1ll*(sum[fa[u]]-1ll*size[u]*(size[u]-1));
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    point=n;
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add1(u,v),add1(v,u);
    }
    for(int i=1;i<=n;i++)
    {
        if(!dfn[i]) 
        {
            tarjan(i,i);
            root=i;
            dfs1(i);
            dfs2(i);
        }
    }
    cout<

你可能感兴趣的:(圆方树)