Comet OJ - Contest #0 D 项链与计数(level 4)(树上并环)(Kruskal+按秩合并并查集)

13 篇文章 0 订阅

题目链接

 

题意:

定义简单环:一个点数和边数相等的回路,并且这条回路上没有出现重复的点或边。

定义项链:

定义 “项链” 是由一些简单环组成的子图,不妨设项链包括 k个简单环 C1C1​, C2​, ……, Ck​ (k \in \mathbb{N}^+x`),那么项链需要满足:

  • 当且仅当 ∣i−j∣≤1 时,简单环 Ci和 Cj 共用顶点;
  • 简单环 Ci 和 Ci+1 恰好共用一个顶点;
  • 任意两个不同的简单环 Ci和 Cj (i≠j) 没有共用边。

 然后给你n个点,m条边,一开始图是空图

操作是按读入顺序把每一条加进去,每加入一条边,你都要计算整个图里面有多少点对(u,v)

满足存在一条项链C1,C2,......,CK(k可以等于1) 使得u∈C1,v∈CK,同时认为(u,v)=(v,u)

假定f(i)为加入i条边之后,满足条件的(u,v)的数量,现在让你求出\bigoplus (i*f(i)))

 

解析:

这道题后来想的时候,把题目化简成,其实你只要维护一棵树就可以了

每加入一条边,如果产生环了,那么就要把整个环合成一个点,并计算贡献

但是当时想不出怎么树上在线并环,因为题目给的是一颗森林,会出现后面两棵树接成一棵树的情况,这样根就没办法确定

这样就没有办法找出并环的时候,环上的所有边。

再到后来还想歪了...想到倒着做,先用无向图tarjan缩点,找环,再一条一条边删掉,每删一条边,用

拓扑排序计算删掉的贡献,但是这样后来也找到了反例。。。。

那么官方题解

双连通分量又分点双连通分量和边双连通分量两种。若一个无向图中的去掉任意一个节点(一条边)都不会改变此图的连通性,即不存在割点(桥),则称作点(边)双连通图。一个无向图中的每一个极大点(边)双连通子图称作此无向图的点(边)双连通分量。求双连通分量可用Tarjan算法


他这里首先就是解决我上面说的根在插入边的时候会变的问题。

就是先把图建成一棵树,这里要以编号为权值,运用Kruskal建生成树,

为什么要用编号小的点?一个环的形成,肯定是有一条最后加入的边,使得其形成一个环。

那么这条边的加入顺序肯定是在这个环上所有边的后面,所以只有当这条边加入的时候,这个环才能

计算贡献。那么我们建树的时候会不会把这条边加入进去呢?不会,因为建树是永远不可能形成环的,

如果不会形成环,并且还把这条边加进入的话,说明这个环里面还有比这条边加入顺序更靠后的边

那么就不满足我们上面假定的条件了。那么如果不按顺序建树的话会出现的后果就是,假如一个环是

应该在假如第6条边的时候,贡献答案的,那么你把第6条边假如到树里面,使得第4条边(环上的边)

没有加入树。那么这个环会在第4条边加入的时候就成环,并贡献答案,这样f(4),f(5)的答案就不对了。

 

然后就是就是合并树上的环了。这个合并环,其实就是暴力版的倍增。

倍增我们是用2的次幂来优化,这里不能用的原因是这里的树是在线变化的,倍增的数组只能离线处理。

那么当加入一条边,使得树上形成环的后,其实就是找加入的这条边的两个端点的LCA,最朴素版的LCA,其实就是用一个while

while(u!=v){
  if(d[u]<d[v]) swap(u,v);
  u=fa[u];
}

每一次,让深度更高的点,向上走一步,这样两个点最后到的一定是他们的LCA

那么我们需要在两个点向上走的过程中,经过的点,都加入到一个并查集里面就可以了,

那么当两个点走到一起时,这个环上的点就都加入到一起了。

 

但是如果你就这么写是会T的,这也是这道题坑的地方...

你在向上合并并查集的时候不能只将ufset[孩子]=父亲,这样这个复杂度就接近于O(n*n)

因为孩子这个并查集里面的点,都还要重新指向父亲。

这里就需要按秩/大小合并并查集....这两个我感觉是差不多,按秩是按树高来合并,其实也就是按并查集大小合并

并查集按大小合并也叫启发式合并,小的指向大的。

那么我们在合并一个点x,以及他的父节点y(注意,父节点可能也是一个环,但是我们现在都把他当作点来处理,在写代码的时候

x真正合并的是findfa(fa[val[x]]),val[]这个我们后面会讲到)

这两个点我们按秩合并的话,假定rank[x]>rank[y],那么y要并入x。正是因为可能存在这种上面的点并入下面的点,所以我们

还需要维护一个val[i],表示i这个并查集(环),深度最低的点,也就是最上面的点。这样我们以后在向上合并的时候,就可以

找到这个并查集的父节点是谁了。

 

这道题还有一个注意的是,他最后的图可能不是连通的,也就是说,我们Kruskal建出来的可能是一颗森林。

那么我们在dfs确定根节点的时候就要用for(int i=1;i<=n;i++) if(!d[i]) dfs(i)

我这个for没加一直在T......

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
const int N=1e6+100;
const int INF=1<<30;

namespace fastIO {
#define BUF_SIZE 1000000
    //fread -> read
    bool IOerror = 0;
    inline char nc() {
        static char buf[BUF_SIZE], *p1 = buf + BUF_SIZE, *pend = buf + BUF_SIZE;
        if(p1 == pend) {
            p1 = buf;
            pend = buf + fread(buf, 1, BUF_SIZE, stdin);
            if(pend == p1) {
                IOerror = 1;
                return -1;
            }
        }
        return *p1++;
    }
    inline bool blank(char ch) {
        return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
    }
    inline void read(int &x) {
        char ch;
        while(blank(ch = nc()));
        if(IOerror)
            return;
        for(x = ch - '0'; (ch = nc()) >= '0' && ch <= '9'; x = x * 10 + ch - '0');
    }
#undef BUF_SIZE
};
using namespace fastIO;


 
struct edge{
    int fr,to,nxt;
    int id;
};
int head[N];
edge f[N*4];
int siz[N];
int fa[N];
int vis[N*2];
int record[N*2][2];
//int ans[N*2];
int ufset[N],m,d[N];
int ran[N];
int val[N];

int fcnt;
void add2(int fr,int to,int id){
    f[fcnt].fr=fr;
    f[fcnt].to=to;
    f[fcnt].id=id;
    f[fcnt].nxt=head[fr];
    head[fr]=fcnt++;
}

int findfa(int x)
{
	if(x==ufset[x]) return x;
	int nex=ufset[x];
	ufset[x]=findfa(nex);
	return ufset[x];
}

void Kruskal()
{
	for(int i=1;i<=m;i++)
	{
		int fx=findfa(record[i][0]);
		int fy=findfa(record[i][1]);
		if(fx!=fy){
			vis[i]=1;
			if(ran[fx]>ran[fy]) ufset[fy]=fx,ran[fx]=max(ran[fx],ran[fy]+1);
			else ufset[fx]=fy,ran[fy]=max(ran[fy],ran[fx]+1);
			add2(record[i][0],record[i][1],i);
			add2(record[i][1],record[i][0],i);
		}
	}
}



void dfs(int x,int fc,int dep)
{
	fa[x]=fc;
	d[x]=dep;
	for(int i=head[x];i!=-1;i=f[i].nxt){
	
		if(f[i].to!=fc){
			dfs(f[i].to,x,dep+1);
		}
	}
}

ll res;

inline int Merge(int u,int v){
	if(ran[u]<ran[v]) swap(u,v);
	ran[u]=max(ran[u],ran[v]+1);
	ufset[v]=u;
	
	if(d[val[u]]>d[val[v]]) val[u]=val[v];
	
	res=res+1ll*siz[u]*siz[v];
	siz[u]+=siz[v];
	return u;
}

inline void solve(int u,int v)
{
	while(u!=v){
		if(d[val[u]]<d[val[v]]) swap(v,u);
		u=Merge(findfa(fa[val[u]]),u);
		u=findfa(u);
		v=findfa(v);
	}
	
}
 
int main(){

	int t;
	//scanf("%d",&t);
	read(t);
    int n;
	while (t--)
	{
		fcnt=0;
		//scanf("%d%d",&n,&m);
		read(n),read(m);
		for(int i=1;i<=n;i++)
		{
			head[i]=-1;
			siz[i]=0;
			ufset[i]=i;
			fa[i]=i;
			d[i]=0;
			ran[i]=1;
		}
		for(int i=1;i<=m;i++) vis[i]=0;
		for(int i=1;i<=m;i++)
		{
			int u,v;
			//scanf("%d%d",&u,&v);
			read(u),read(v);
			record[i][0]=u,record[i][1]=v;
		}
		Kruskal();
		for(int i=1;i<=n;i++)
			if(!d[i]) dfs(i,0,1);
		for(int i=1;i<=n;i++) ufset[i]=i,siz[i]=1,ran[i]=1,val[i]=i;
		ll ans=0;
		res=0;
		for(int i=1;i<=m;i++)
		{
			if(vis[i]==0) {
				int u=findfa(record[i][0]);
				int v=findfa(record[i][1]);
				if(u!=v)
				{
					solve(u,v);
				}
			}
			ans=ans^(i*res);
		}
		printf("%lld\n",ans);
	}
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值