CSP2019D1T3 树上的数【贪心+链表(并查集)维护偏序】

题目描述:

洛谷P5659 树上的数

题目分析:

先看看出题人的题解:如何评价 CSP-J/S 2019 第二轮? 知乎
容易想到贪心,每次用最小的数字找到可以匹配的最小编号。

如果每次标记一条路径,我们就是要想办法维护一个边的偏序关系:
在这里插入图片描述

一个点的邻接边相当于形成了一个个链表的关系,选了x就必须接着选y。一次路径标记相当于将路径上的每个点的两条边对应的链表点连接起来。
可以从 x → y → z x\rarr y\rarr z xyz意味着在 y y y的邻接边中:

  • ( x , y ) (x,y) (x,y) ( y , z ) (y,z) (y,z)不在同一个链表中
  • ( x , y ) (x,y) (x,y)没有紧接着要走的边(即没有后继)
  • ( y , z ) (y,z) (y,z)不是某一条边紧接着要走的边(即没有前驱)
  • ( x , y ) (x,y) (x,y) ( y , z ) (y,z) (y,z)相连后,若起点与终点连通了,则不能存在还没有接入的边。

可以建立一个链表头(用0表示,即起点,最先选择的边)和一个链表尾(用n+1表示,即终点,最后选择的边),那么起点和终点就可以和中间点用同样的处理方式了。判断1和判断4可以通过并查集和记录连通块个数实现。

最后每个点一定有作为起点、终点的边,所有的邻接边一定会形成一条完整的链。

这题最重要的一点就是等价翻译题意得到PPT中列举的三个条件。、

Code:

#include<bits/stdc++.h>
#define maxn 2005
using namespace std;
int T,n,a[maxn],fa[maxn],f[maxn][maxn],ans[maxn],blk[maxn],Mn;
bool bf[maxn][maxn];
int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot;
inline void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
int find(int *F,int x){return F[x]==x?x:F[x]=find(F,F[x]);}
void dfs(int u){
	if(f[u][fa[u]]==fa[u]&&!bf[u][n+1]&&(blk[u]<=2||find(f[u],0)!=fa[u])) Mn=min(Mn,u);
	for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=fa[u]){
		fa[v]=u;
		if(f[u][fa[u]]==fa[u]&&find(f[u],v)!=fa[u]&&!bf[u][v]&&(blk[u]<=2||find(f[u],0)!=fa[u]||find(f[u],v)!=n+1)) dfs(v);
	}
}
int main()
{
	int x,y,z;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		memset(fir,0,(n+1)<<2),tot=0;
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),blk[i]=2;
		for(int i=1;i<n;i++) scanf("%d%d",&x,&y),line(x,y),line(y,x),blk[x]++,blk[y]++;
		for(int i=1;i<=n;i++) for(int j=0;j<=n+1;j++) f[i][j]=j,bf[i][j]=0;
		for(int i=1;i<=n;i++){
			Mn=n+1,fa[a[i]]=0,dfs(a[i]),ans[i]=Mn;
			x=fa[Mn],y=Mn,z=n+1;
			while(y){
				f[y][x]=z,bf[y][z]=1,blk[y]--;
				z=y,y=x,x=fa[x];
			}
		}
		for(int i=1;i<=n;i++) printf("%d%c",ans[i],i==n?10:32);
	}
}

有小伙伴在问我实现细节,补充一下:

把与 i i i 相连的边看做点,边 ( i , j ) (i,j) (i,j) 用点 j j j 来表示,我们是要维护一个 i i i 周围的邻接点组成的一个个链表,其中包含起点 0,终点 n+1

f [ i ] [ j ] f[i][j] f[i][j] 表示在 i i i 点的链表中 j j j 的后继。初始时 f [ i ] [ j ] = j f[i][j]=j f[i][j]=j,代表没有后继,这跟并查集是一样的。
b f [ i ] [ j ] bf[i][j] bf[i][j] 表示在 i i i 点的链表中 j j j 是否有前驱。初始时是一个个散点,所以 b f [ i ] [ j ] = 0 bf[i][j]=0 bf[i][j]=0

判断 ( i , j ) (i,j) (i,j) 这条边在 i i i 的链表里是否有后继就是判断 f [ i ] [ j ] = = j f[i][j]==j f[i][j]==j
如果 ( f a [ u ] , u ) (fa[u],u) (fa[u],u) 这条边在 u u u 的链表中没有后继, ( u , v ) (u,v) (u,v) 没有前驱,那么我们想把这两条边接起来,就要两个点是否在不同的链表中,即 ( u , v ) (u,v) (u,v) 是否能够走到 ( f a [ u ] , u ) (fa[u],u) (fa[u],u),即是否有 f i n d ( f [ u ] , v ) = = f a [ u ] find(f[u],v)==fa[u] find(f[u],v)==fa[u]

b l k [ i ] blk[i] blk[i] 就是 i i i 点的链表有多少条,初始时每条边单独成一个链表,加上起点终点,总共度数+2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值