比较常规的一道构造题,练习自己的构造水平。
首先对于一条边 \((u,v)\),如果有边 \((x,u),(v,y)\),我们可以对 \(x,y\) 的距离不超过 \(2\) 的点集 \(S_x,S_y\) 进行求交 \(S_x\cap S_y\),结果恰好就是 \(\{u,v\}\)。
我们枚举两条信息,对两个集合求交,如果结果为两个点,那么这两个点之间连一条边。
注意当 \(n=2\) 时要特判。
但是如果没有 \(x,y\) 这两个点就不能判断。具体的,我们只能用这种方式连接所有非叶子,即度数 \(>1\) 的点。
这里有个隐藏的点,我们可以间接得知哪些点是叶子,哪些点不是叶子。
特别的,如果一条边都连不了,那么树是菊花图,特判掉。
接下来处理叶子,我们要给每个叶子找一个非叶子连接。
对于叶子 \(u\),我们可以找到点 \(u\) 对应的信息,为包含 \(u\) 的最小点集,即我们可以确定 \(S_u\)。枚举非叶子 \(v\),判定 \((u,v)\) 是否合法:
考虑处理出 \(to_v\) 表示与 \(v\) 距离 \(\le 1\) 的非叶点集,令 \(S_u'\) 为 \(S_u\) 去掉叶子后的点集,当 \(to_v\) 和 \(S_u'\) 完全相同时合法。
有个值得思考的点:如果有多个信息满足点集包含 \(u\),且都是最小的,说明 \(v\) 下面挂着的叶子不止 \(u\),进而可知这些集合长得都一样。
但是过不了样例 2,原因是存在两个非叶子 \(v_1,v_2\),满足 \(to_{v_1}\) 和 \(to_{v_2}\) 相同。
仔细思考一下,发现只有非叶子个数为 \(2\) 的情况才会出现这种情况,特判即可。
显然 bitset 优化,时间复杂度 \(O(\dfrac {n^3} \omega)\)。
启示:
-
循序渐进,步步思考
-
列清思路列表,理清特判点
-
比较发散,一定要肯思考,不要轻易放弃
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned ll
#define fi first
#define se second
#define pir pair<ll,ll>
#define mkp make_pair
#define pb push_back
using namespace std;
const ll maxn=1010, inf=8e18;
ll n, tot, lf[maxn], vis[maxn];
bitset<maxn> mp[maxn], to[maxn], unlf_mp[maxn], tmp;
int main(){
scanf("%lld",&n);
if(n==2) {puts("1 2"); return 0;}
for(ll i=1;i<=n;i++){
ll m, x; scanf("%lld",&m);
tot+=m;
while(m--){
scanf("%lld",&x);
mp[i][x]=1;
}
}
if(tot==n*n){
for(ll i=2;i<=n;i++) printf("1 %lld\n",i);
return 0;
}
for(ll i=1;i<=n;i++) lf[i]=1;
for(ll i=1;i<=n;i++)
for(ll j=i+1;j<=n;j++){
tmp=mp[i]&mp[j];
if(tmp.count()==2){
ll x=tmp._Find_first(), y=
tmp._Find_next(x);
to[x][y]=to[y][x]=1, lf[x]=lf[y]=0;
}
}
ll cnt=0;
for(ll i=1;i<=n;i++)
for(ll j=i+1;j<=n;j++)
if(to[i][j]) printf("%lld %lld\n",i,j), ++cnt;
if(cnt==1){ ll x=0, y=0;
for(ll i=1;i<=n;i++)
if(!lf[i]) vis[i]=1, y=x, x=i;
for(ll i=1;i<=n;i++)
if(mp[i].count()<n){
for(ll j=1;j<=n;j++)
if(lf[j]&&mp[i][j])
vis[j]=1, printf("%lld %lld\n",j,x);
break;
}
for(ll i=1;i<=n;i++)
if(!vis[i]) printf("%lld %lld\n",i,y);
return 0;
}
for(ll i=1;i<=n;i++) unlf_mp[i]=mp[i], to[i][i]=1;
for(ll i=1;i<=n;i++)
for(ll j=1;j<=n;j++)
if(lf[j]) unlf_mp[i][j]=0;
for(ll i=1;i<=n;i++)
if(lf[i]){ ll id=0;
for(ll j=1;j<=n;j++)
if(mp[j][i]&&(!id||mp[id].count()>mp[j].count()))
id=j;
for(ll j=1;j<=n;j++)
if(unlf_mp[id]==to[j]){
printf("%lld %lld\n",i,j);
break;
}
}
return 0;
}