CF1391 E Pairs of Pairs 图论 dfs树 构造

题目链接

题意:
t t t组询问,每组询问给你一个 n n n个点 m m m条边的无向简单连通图(没有重边和自环),你要做到以下两个要求之一:
1.找到一条长度大于 ⌈ n 2 ⌉ \lceil \frac n 2 \rceil 2n的路径,输出路径上点的个数,并按照路径经过点的顺序输出这些点;
2.找到一个多于 ⌈ n 2 ⌉ \lceil \frac n 2 \rceil 2n个点的集合,集合需要满足以下条件:集合由若干二元组构成,每个二元组表示图上两个不同点,每个点最多只能在一个二元组中出现,要使得任意集合内的二元组 ( a , b ) (a,b) (a,b) ( c , d ) (c,d) (c,d)之间均满足集合 { a , b , c , d } \{a,b,c,d\} {a,b,c,d}中任何一个点与其他最多两个点有连边。要求输出二元组个数以及每个二元组包含的元素。
∑ n < = 5 e 5 \sum n<=5e5 n<=5e5      ∑ m < = 1 e 6 \ \ \ \ \sum m<=1e6     m<=1e6

题解:
一个构造题。我当时比赛时会第一个条件,但是对于第二个找集合有一些思路,但是还是出了点偏差。主要是我一开始找这个最长路径时并不是用的dfs树,所以没想到第二问答案的构造方法。

第一个要求就是去找一个图上最长路,我的想法和找树的直径差不多,用两遍dfs,每次构建dfs树。先随便选一个点(我选的是1号点),dfs一遍找到离它最远的点,再用这个点为起点做一遍dfs,再找最远的点,如果能找到一条符合条件的路径,那么我们就完成了任务。

第二个要求,我想到了选二元组时应该尽可能选组内没有连边的两个点,但是脑子里感觉正确性有保证的想法只有建原图的补图然后跑二分图匹配或者网络流,显然复杂度是炸了的。这里用dfs树就很好的解决了这个问题。

正解的做法是,在求dfs树时顺便维护出每个点的深度,在dfs树上深度相同的点之间一定是没有边的。证明的话可以反证法,假设存在dfs树上深度相同的两个点之间有边,那么在dfs时一定可以在先访问的那个点到后访问的那个点,它们的深度不应该相同,与它们深度相同的假设矛盾,故假设不成立。

这样我们对于所有深度相同的点进行两两配对,最后对于每一个深度,最多剩下一个点,由于第一个任务没有完成,那么最大深度是小于 ⌈ n 2 ⌉ \lceil \frac n 2 \rceil 2n的,那么每个深度都剩下一个点,也最多剩下 ⌊ n 2 ⌋ \lfloor \frac n 2 \rfloor 2n个点,可以满足题意。

我一开始以为标算有问题,后来想想是我傻了。标算对于第一个要求并没有一定找到那条最长的路径,它只做了一遍dfs,但是在此基础上直接做第二个任务也是可以满足要求的,因为它的dfs树深度小于 ⌈ n 2 ⌉ \lceil \frac n 2 \rceil 2n,可以满足那个剩下点不超过 ⌊ n 2 ⌋ \lfloor \frac n 2 \rfloor 2n的性质,于是也是正确的。

代码:

#include <bits/stdc++.h>
using namespace std;

int t,n,m,cnt,hed[500010],fa[500010],dis[500010];
int dep[500010],ans,vis[500010],p[500010];
struct node
{
	int from,to,next;
}a[2000010];
queue<int> q,q1,q2;
vector<int> v[500010];
inline void add(int from,int to)
{
	a[++cnt].from=from;
	a[cnt].to=to;
	a[cnt].next=hed[from];
	hed[from]=cnt;
}
inline void dfs(int x)
{
	vis[x]=1;
	for(int i=hed[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(vis[y])
		continue;
		dep[y]=dep[x]+1;
		fa[y]=x;
		dfs(y);
	}
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		cnt=0;
		for(int i=1;i<=m;++i)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			add(x,y);
			add(y,x);
		}
		dep[1]=1;
		dfs(1);
		int mx=0,ji=0;
		for(int i=1;i<=n;++i)
		{
			if(dep[i]>mx)
			{
				mx=dep[i];
				ji=i;
			}
			dep[i]=0;
			fa[i]=0;
			vis[i]=0;
		}
		dep[ji]=1;
		dfs(ji);
		mx=0;
		ji=0;
		for(int i=1;i<=n;++i)
		{
			if(dep[i]>(n+1)/2)
			{
				ji=i;
				mx=dep[i];
				break;
			}
		}
		if(ji!=0)
		{
			printf("PATH\n");
			printf("%d\n",mx);
			for(int i=ji;i;i=fa[i])
			printf("%d ",i);
			printf("\n");
			for(int i=1;i<=n;++i)
			{
				hed[i]=0;
				dep[i]=0;
				fa[i]=0;
				vis[i]=0;
			}
			continue;
		}
		for(int i=1;i<=n;++i)
		v[dep[i]].push_back(i);
		ans=0;
		for(int i=1;i<=n;++i)
		{
			for(int j=0;j<v[i].size();++j)
			{
				if(j%2)
				{
					++ans;
					p[v[i][j]]=v[i][j-1];
					p[v[i][j-1]]=v[i][j];
				}
			}
		} 
		printf("PAIRING\n");
		printf("%d\n",ans);
		for(int i=1;i<=n;++i)
		{
			if(p[i]>i)
			printf("%d %d\n",i,p[i]);
		}
		for(int i=1;i<=n;++i)
		{
			v[dep[i]].clear();
			hed[i]=0;
			dep[i]=0;
			fa[i]=0;
			vis[i]=0;
			p[i]=0;
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值