[SDOI2018]战略游戏,洛谷P4606,圆方树+虚树

正题

      看到这样的性质就可以想到圆方树,而两点之间贡献的答案就是路径上的圆点数量,为了去重,我们只需要将虚树建出来求路径上的点权和就行了,实际上并不需要建出来,可以发现按照dfs序排一下之后就可以考虑欧拉回路,只需要将每个点的深度加起来-相邻两点的lca深度即可,这里的深度是带权深度,相当于点到根的圆点数量,最后这个虚树的权值也并不是正确的,因为在所有点的lca的父亲到根节点的圆点实际上并不会贡献答案,减一下就可以了.

#include
using namespace std;

const int N=400010;
struct edge{
	int y,nex;
}s[N<<1],e[N];
int first[N],fir[N],len,le,n,m,tim,q,c;
int dfn[N],low[N],dep[N],fa[N][20],sz[N],a[N];
int sta[N],top,num;

void ins(int x,int y){s[++len]=(edge){y,first[x]};first[x]=len;}
void inss(int x,int y){e[++le]=(edge){y,fir[x]};fir[x]=le;}
bool cmp(const int&x,const int&y){return dfn[x]=dfn[s[i].y]) inss(num,sta[top]),top--;
				inss(x,num);
			}
		}
		else low[x]=min(low[x],dfn[s[i].y]);
	}
}

void dfs(int x){
	dfn[x]=++tim;
	for(int k=1;k<=19;k++) fa[x][k]=fa[fa[x][k-1]][k-1];
	for(int i=fir[x];i;i=e[i].nex){
		fa[e[i].y][0]=x;
		dep[e[i].y]=dep[x]+1;
		sz[e[i].y]=sz[x]+(e[i].y<=n);
		dfs(e[i].y);
	}
}

int lca(int x,int y){
	if(dep[x]>=dep[y]) swap(x,y);
	for(int k=19;k>=0;k--) if(dep[fa[y][k]]>=dep[x]) y=fa[y][k];
	if(x==y) return x;
	for(int k=19;k>=0;k--) if(fa[x][k]!=fa[y][k]) x=fa[x][k],y=fa[y][k];
	return fa[x][0];
}

int main(){
	int T,x,y;
	long long ans;
	scanf("%d",&T);
		while(T--){
		prepare();
		scanf("%d %d",&n,&m);num=n;
		for(int i=1;i<=m;i++) scanf("%d %d",&x,&y),ins(x,y),ins(y,x);
		Tarjan(1);dep[1]=sz[1]=1;tim=0;dfs(1);
		scanf("%d",&q);
		while(q--){
			scanf("%d",&c);
			for(int i=1;i<=c;i++) scanf("%d",&a[i]);
			sort(a+1,a+1+c,cmp);
			ans=sz[a[1]]-sz[fa[lca(a[1],a[c])][0]];
			for(int i=2;i<=c;i++) ans+=sz[a[i]]-sz[lca(a[i-1],a[i])];
			printf("%lld\n",ans-c);
		}
	}
}

 

你可能感兴趣的:(点双联通分量,圆方树,虚树)