并查集+生成树回顾专题

POJ1679

问并查集是否唯一

题解:考虑kruscal,从小到大将边相同的分为一组。然后对于每一组单独考虑,筛掉与之前组冲突的边(先不合并)。然后将剩下的边用个并查集判断一下即可。

POJ2912

题意:n个人剪刀石头布,每个人只能出一种。但是有一个裁判可以随意出。给出比赛信息,问n个人中是否有裁判,以及裁判在至少第几个可以确定?

枚举裁判,然后忽略掉裁判,剩下的用一个并查集来判断是否合法。具体的判断过程是,如果两个人的胜负关系确定,那么他们所在的并查集的根的胜负关系也确定。然后我们可以用相对距离来衡量胜负关系。然后合并的时候,先维护出fa和两个端点分别到根的相对路径。推到出根的相对路径。然后再合并根。查询也同理。然后根据是否能满足多个裁判,已经所有人在作为裁判时,至少在第几个比赛结果发生冲突来得出答案。

POJ2728

问一个 ∑ c o s t / l e n \sum{cost/len} cost/len最小的生成树

01分数规划,没什么好说。但是这题是稠密图,然后卡了kruscal。只能写prim。prim大概思想就是在确定了的点和未确定的点之间找最短边来生成树即可,然后每次用确定了的新点来更新新的最短边。注意分数规划方向别搞反了。

POJ1639

对于一个确定的根节点有k度数限制的最小生成树

算法如下:
1.先不考虑根节点,做最小生成树,得到m个连通块。若m>k则这样的生成树不存在。然后从根节点连向m个连通块连最短路径得到度数为m的生成树。
2.从根节点跑个dp,dp[i]表示从根节点到i所经过路径的最大值。若直接相连则为-inf
3.找出一个edge[1][i]-dp[i]最小的边,连上该边,删除dp[i]的边。该边必须小于0,否则算法结束。重复2-3找到答案

POJ3164

最小树形图板子题(有向图的最小生成树)

算法如下:
1)求最短弧集合 E
2)判断集合 E 中有没有有向环,如果有转步骤 3,否则转 4
3)收缩点,把有向环收缩成一个点,并且对图重新构建,包括边权值的改变和点的处理,之后再转步骤 1
4)展开收缩点,求得最小树形图

具体看板子,如果要求最小树形图的方案的话。不能直接考虑,因为若当前边删除了,以后可能会加回来。这样我们就记录删除的方案,然后从后往前时间回溯获得最后边的存在状况即可

若无规定起点的最小树形图则选一个超级源点,对1~n都连边为inf。然后途中如果有起点为超级源点的边被选入最短路集合的话,那么终点即是树形图的起点

牛客多校第8场E

有(1<=n<=1e5)个点,(1<=m<=1e5)条边,每条边有两个权值l,r。表示只有编号为l~编号为r的人才能通过这条边。问有多少人可以从1走到n

线段树+并查集
首先我们将每一条边下放到线段树的一个节点上。这样就不用管图了。然后我们可以在线段树上跑dfs,每次遇到一个节点就更新并查集,如果当前1和n在一个并查集上就更新答案。然后麻烦的是回溯。我们记录一下未合并的并查集长啥样,然后还原回去即可。但是这个并查集不能压缩路径(否则回不去啦),需要注意的一点是线段树上的区间表示最好用左闭右开,这样比较方便,不用塞点。具体看代码

#include
#define pii pair
#define fi first
#define se second
#define lc o*2
#define rc o*2+1
#define ll long long
using namespace std;
const int maxn = 400005;
int n,m;
int a[maxn],b[maxn],l[maxn],r[maxn];
vector<pii>V[4*maxn];
int fa[maxn],sz[maxn];//按秩合并的并查集,不压缩路径 
ll ans = 0;
int find(int x) {
	return x==fa[x]?x:find(fa[x]);
}
int c[maxn],cnt;
void update(int o,int L,int R,int ql,int qr,int id) {
	if(ql<=L&&R<=qr) {
		V[o].push_back({a[id],b[id]});
		return;
	}
	int M = (L+R)>>1;
	if(ql<=M)update(lc,L,M,ql,qr,id);
	if(qr>M)update(rc,M+1,R,ql,qr,id);
}
void query(int o,int L,int R) {
	int flag=0;
	vector<int>tmp;
	vector<pii>cc;
	for(auto t:V[o]) {
		int fx = find(t.fi);
		int fy = find(t.se);
		if(fx==fy)continue;
		if(sz[fx]<sz[fy]) {
			swap(fx,fy);
		}
		fa[fy]=fx;
		cc.push_back({fx,sz[fx]});//记录之前的样子 
		tmp.push_back(fy);
		sz[fx]+=sz[fy];
	}
	if(find(1)==find(n)) {
		ans += (c[R+1]-c[L]);//如果能连通,统计答案,左闭右开,所以要c[r+1] 
		flag=1;
	}
	int M = (L+R)>>1;
	if(!flag&&L<R) {
		query(lc,L,M);
		query(rc,M+1,R);
	}
	for(auto t:tmp) {
		fa[t]=t;
	}
	for(auto t:cc){
		sz[t.fi]=min(sz[t.fi],t.se); 
	}//样子变回去 
}
int main() {
	srand(time(NULL));
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)fa[i]=i,sz[i]=1;
	for(int i=1; i<=m; i++) {
		scanf("%d%d%d%d",&a[i],&b[i],&l[i],&r[i]);
		c[++cnt]=l[i];
		c[++cnt]=r[i]+1;//左闭右开区间 此时每个叶子节点都是该节点为左闭到后面一位为右开的区间 
	}
	sort(c+1,c+1+cnt);
	cnt=unique(c+1,c+1+cnt)-(c+1);
	for(int i=1; i<=m; i++) {
		int tmpl = lower_bound(c+1,c+1+cnt,l[i])-c;
		int tmpr = lower_bound(c+1,c+1+cnt,r[i]+1)-c;
		update(1,1,cnt,tmpl,tmpr-1,i);
	}
	query(1,1,cnt);
	cout<<ans<<endl;
	return 0;
}
/*
5 5
1 2 1 5
2 3 1 3
3 5 3 5
2 4 1 3
4 5 3 5
*/

你可能感兴趣的:(图论)