有一棵 (n) 个节点的树,只给出每个点与其距离不超过 (2) 的点的集合(包含自己),要求构造出一棵符合要求的树,保证有解。
(nle 1000) 。
思维不难,但是细节很多,不愧是评分 3200
的题(也可能是我的方法太怪了)。
一个显然的方法就是减小数据大小递归求解,考虑如何找出树的某些叶子,可以发现,如果存在 (x) 个集合一样且这些集合的大小为 (x+2) ,那么这些集合对应的点就是一堆叶子,如果只看这些叶子和它们的父亲就是一个菊花图,显然任何形态的树都存在这一结构,如果能找到另外一个集合和这种集合的交的大小为 (2) ,显然在交中的点不是叶子,这就意味着,当目前的树的高度大于 (2) 时,我们一定可以靠这种方法找出这一结构并找出叶子,然后将可以减小规模递归求解。
当树的高度小于等于 (2) 时,那么目前的树是一个菊花图,剩下的所有集合都应该是相同的,可以发现,如果有一个原集合和现在的集合交的大小为 (1) ,那么在交中的点一定不是根,如果交的大小为 (2) ,那么在交中的点一定包含了根,然后枚举一遍做交就可以找出根了。
然后从根往回递归,记得存之前找出的叶子的父亲和爷爷节点,根据已经形成的树判断那两个点哪个为父亲哪个为爷爷。
找叶子时需要找到出一个交为 (2) 的点,可以预处理出哪两个集合交为 (2) ,判断交要用 bitset 优化,复杂度可降为 (mathcal{O(frac{n^3}{w})}) 。
判断两个集合是否相同可以用随机化 hash ,对每个点打上一个 ([1,2^{64}]) 的随机数,然后一个集合的 hash 值就是集合的点的随机数的异或和,删除一个点就再异或这个点的随机数,可以发现出锅概率大概是 (frac{1}{2^{64}}) 。
需要注意的细节:
-
要对 (n=2) 特判。
-
每次找叶子就只找同一个父亲的叶子删除,不然连样例 2 都过不了(其实是我的方法有一点小锅,但是我还没有想到怎么样才能解决)。
其实我一开始找叶子的方法不是这样的,后来假了三四次才找到了正确方法,所以做题时一定要想清楚了再写啊qaq(但这并不妨碍我骂出题人)。
MDFK = 莫队分块(确信)
已经去掉了 3K 的调试代码后的代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int M=1005;
int read(){
int x=0,y=1;char ch=getchar();
while(ch<'0'||ch>'9') y=(ch=='-')?-1:1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*y;
}
ll RAND(){
ll res=0;
for(int i=1;i<=17;i++) res=res*10ll+rand()%10;
return res;
}
int tot=0,first[M];
struct Edge{ int nxt,to; }e[M*M];
void add(int x,int y){
e[++tot]=(Edge){first[x],y};
first[x]=tot;
}
ll IH[M],Hash[M];
int n,sy,SZ[M],fa[M][2];bool vis1[M],vis2[M],vis3[M];
vector<int> cun[M],hhh[M];bitset<M> jh[M],fuc[M],fz;
struct FUCK{ ll h;int id; }FFF[M];
bool cmp(FUCK x,FUCK y){ return x.h<y.h; }
int MDFK[M];
void work(int cs){
int cnt=0,CNT=0;
for(int i=1;i<=n;i++) if(!vis1[i]) FFF[++CNT]=(FUCK){Hash[i],i};
sort(FFF+1,FFF+CNT+1,cmp);
for(int i=1;i<=CNT;i++){
int j=i;
while(j<CNT&&FFF[j+1].h==FFF[i].h) j++;
if(j-i+1==SZ[FFF[i].id]-2) for(int k=i;k<=j;k++) MDFK[++cnt]=FFF[k].id;
i=j;
}
int minn=1e9,u=1;
for(int i=1;i<=cnt;i++){
if(Hash[MDFK[i]]!=Hash[MDFK[i+1]]||i==cnt){
if(SZ[MDFK[i]]<minn) minn=SZ[MDFK[i]],u=i;
}
}
int fzcnt=0;ll NH=Hash[MDFK[u]];
for(int i=1;i<=cnt;i++) if(Hash[MDFK[i]]==NH) MDFK[++fzcnt]=MDFK[i];
cnt=fzcnt;
if(!cnt){
int rt=1,u;
for(int i=1;i<=n;i++) if(!vis1[i]) u=i;
for(int i=1;i<=n;i++){
if(!vis1[i]) continue ;
fz=fuc[i]&jh[u];
if(fz.count()==1) for(int j=1;j<=n;j++) if(fz[j]) vis3[j]=1;
if(fz.count()==2) for(int j=1;j<=n;j++) if(!fz[j]) vis3[j]=1;
}
for(int i=1;i<=n;i++) if(!vis2[i]&&!vis3[i]) rt=i;
for(int i=1;i<=n;i++) if(!vis2[i]&&i!=rt) printf("%d %d
",fa[i][0]=rt,i);
return ;
}
for(int l=1;l<=cnt;l++){
int i=MDFK[l],u;
for(int j=first[i];j;j=e[j].nxt){
int v=e[j].to;
if(!vis1[v]) u=v;
}
fz=fuc[i]&fuc[u];
int x=fz._Find_first(),y=fz._Find_next(x);
for(int j=1;j<=n;j++){
if(jh[i][j]&&j!=x&&j!=y){
if(vis2[j]) continue ;
vis2[j]=1;hhh[cs].push_back(j);
fa[j][0]=x,fa[j][1]=y;
int sz=cun[j].size();
for(int k=0;k<sz;k++){
int v=cun[j][k];
jh[v][j]=0;Hash[v]^=IH[j];SZ[v]--;
}
}
}
}
for(int i=1;i<=cnt;i++) vis1[MDFK[i]]=1;
work(cs+1);
int FUKC=hhh[cs].size();
for(int i=0;i<FUKC;i++){
int j=hhh[cs][i];
if(fa[fa[j][1]][0]==fa[j][0]) swap(fa[j][0],fa[j][1]);
printf("%d %d
",fa[j][0],j);
}
}
void solve(){
sy=n=read();
for(int i=1;i<=n;i++) IH[i]=RAND();
for(int i=1;i<=n;i++){
int sz=read(),x;
for(int j=1;j<=sz;j++) x=read(),jh[i][x]=fuc[i][x]=1,cun[x].push_back(i),Hash[i]^=IH[x];
}
if(n==2) return (void)(printf("1 2
"));
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++){
int x=(jh[i]&jh[j]).count();
if(i==j) SZ[i]=x;
else if(x==2) add(i,j),add(j,i);
}
work(0);
}
signed main(){
srand(time(NULL));
solve();
}