这是我所看了题解的题目。看完题解还厚颜无耻地在vp结束之前写了交上去
洛谷题目页面传送门 & CF题目页面传送门
给定一个(n)个点(m)条边的连通无向图。你需要做以下两件事中的任意一件:
- 找一条长度至少为(leftlceildfrac n2 ight ceil)的简单路径;
- 将至少(leftlceildfrac n2 ight ceil)个节点(要求是偶数个)两两分组,满足任意两组的四个节点的导出子图最多有两条边。
可以证明至少有一件事是可以做的。
本题多测。
(sum ninleft[2,5 imes10^5 ight],sum minleft[1,10^6 ight])。
无向连通图有个重要的东西:DFS树。它有一个很重要的性质:两个点之间有边仅当它们是长辈与晚辈的关系。很好证,因为若有非长晚辈关系的点对之间有边,那么当DFS到其中那个时间戳较小的点时,根据深度优先一定可以走到时间戳较大的那个点,从而使得它们是长晚辈关系,与假设矛盾。这与有向图DFS树的横叉边的性质类似。
看到第二件事中有要限制一些边不能出现这种感觉,不难想到DFS树的这个性质。(这个大概是Tarjan求DCC时要用到的,然鹅我还不会,所以没想到)
求出任意DFS树,如果存在深度(geqleftlceildfrac n2 ight ceil)的点那么显然可以做第一件事,直接输出就行了。否则,
注意到DFS树的这一性质,如何把它用起来。考虑两对深度不同的兄弟节点,兄弟之间显然不会有边,那么只有在不同对中的边。考虑从下往上连,由于只能连向祖先,而上面两个点是兄弟可以推出对于下面每个点最多只有上面的一个点作为它的祖先,于是最多会有两条边。
于是问题就迎刃而解了。接下来同层的尽量分组,每层最多留下一个单身。由于最大深度(<leftlceildfrac n2 ight ceil),单身的也是小于这么多,可以保证被分组的点的数量。
并不能保证下次遇到这样的智商题能做出来哦(
多测memset
,爆零两行泪。
代码:
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=500000;
int n,m;
vector<int> nei[N+1];
bool vis[N+1];
int fa[N+1],dep[N+1];
void dfs(int x=1){//求搜索树
vis[x]=true;
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
if(vis[y])continue;
fa[y]=x;dep[y]=dep[x]+1;
dfs(y);
}
}
vector<int> buc[N+1];
void mian(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)nei[i].clear(),buc[i].clear(),vis[i]=fa[i]=dep[i]=0;
while(m--){
int x,y;
scanf("%d%d",&x,&y);
nei[x].pb(y);nei[y].pb(x);
}
dep[1]=1;
dfs();
for(int i=1;i<=n;i++)
if(dep[i]>=n+1>>1){//可以输出简单路径了
puts("PATH");
printf("%d
",dep[i]);
while(i)printf("%d ",i),i=fa[i];
puts("");
return;
}
else buc[dep[i]].pb(i);
puts("PAIRING");//不能输出简单路径,那么一定能分组
int cnt=0;
for(int i=1;i<=n;i++)cnt+=buc[i].size()>>1;//统计数量
printf("%d
",cnt);
for(int i=1;i<=n;i++){//枚举层
for(int j=0;j+1<buc[i].size();j+=2)printf("%d %d
",buc[i][j],buc[i][j+1]);//同层分组
}
}
int main(){
int testnum;
cin>>testnum;
while(testnum--)mian();
return 0;
}