CF 566E Restoring Map
在WC时,jmr在他的《集训队作业选作》中给出了这个题的精确解答
这算是我为数不多听明白的题目了
人生中第一道黑题!!!
思路
因为题目中并没有将集合与点的对应关系告诉我们
所以相当于已知一个大集合,其中下辖着各个小集合,小集合中涵盖着点
这就引导着我们朝着集合的角度来思考答案
在我们还没有分析题目之前,先做几个简单但重要的事,这是之后证明的前提
1 判菊花图
2 特判n=2或3
设\(f_u\)为\(u\)的距离\(≤2\)的集合
首先可以分析出当两个点的距离大于4时,没有所在集合没有交点
当距离等于4时,可以判出一个点(但这并没有什么用)
当距离等于3时,可以判出两个节点,并且均不为叶节点
小于2时,我们发现似乎讨论不太出更多性质(3就够了
据此我们可以将一棵树中的所有非叶节点都筛出来,将该集合记为\(releaf\),我们可以知该集合中任意元素均为非叶节点,接下来我们证明其必要性
\(prove\):
假设我们已经充分的对集合两两求交,\(releaf\)已经完善
设存在一个非叶节点\(u\),\(s.t.\) \(u\notin releaf\),\(u\)必然存在一条边\(E\),在\(E\)上另一点\(v\in releaf\)因为\(v,u\)非叶节点,那么对其子节点进行集合求交,则可以筛出\(u,v\),但\(releaf\)并不存在\(u,v\),所以假设不成立
现在我们已有\(releaf\),非常自然地将这个题转化为向\(releaf\)中的点挂叶节点,并且我们可以建出\(releaf\)树,借助这个树,我们可以处理与树中点\(u\)距离\(≤1\)的点集\(g_u\),然后我们可以发现一个点\(v\)可以挂在\(u\)上,那么其满足\(g_u\)\(=\)\(f_v \ \ and \ \ releaf\)
但是我们又遇到了一个问题,如果\(v\in releaf\),那么也满足上式,为了解决这个问题,我们决定研究一下怎么判出集合与点的对应关系
先给出结论:
对于一个叶节点 x,包含 x 的\(f_x\)中元素个数最小的\(f\)就是 x 的\(f_x\)
我不打算证明了
算法复杂度\(O(\frac{n^3}{w} )\)
\(detail\)
算法中\(bitset\)中寻找\(1\)不能使用\(for\)循环,若使用复杂度不会除\(w\)
所以使用\(\_\ Find \_\ first\) 和 \(\_\ Find \_\ next\)
前者是找第一个1,后者是找下一个1
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<set>
using namespace std;
#define fx(x) _Find_next(x)
#define ff() _Find_first()
#define int long long
#define INF 1<<30
const int p=1e3+5;
template<typename _T>
inline void read(_T &x)
{
x=0;char s=getchar();int f=1;
while(s<'0'||s>'9') {f=1;if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=f;
}
bitset<p> b[p];
bitset<p> g[p];
bitset<p> ans[p];
bitset<p> releaf;
int tit;
int a[p];
int vis[p];
bool c[p][p];
bitset<p> lef,rig;
signed main()
{
int n;
read(n);
if(n == 2){cout<<1<<" "<<2;return 0;}
if(n == 3){cout<<1<<" "<<2<<'\n'<<2<<" "<<3<<'\n';return 0;}
for(int i=1,x;i<=n;i++)
{
read(x);
for(int j=1,y;j<=x;j++)
{
read(y);
b[i].set(y-1);
}
}
releaf = b[1];
for(int i=2;i<=n;i++) releaf&=b[i];
if(releaf.count() == n){
for(int i=2;i<=n;i++)
{
cout<<1<<" "<<i<<'\n';
}
return 0;
}
releaf.reset();
bitset<p> op;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i == j) continue;
op = b[i]&b[j];
int x = op._Find_first();
int y = op._Find_next(x);
if(op.count()==2)
{
g[x+1].set(y);
g[x+1].set(x);
g[y+1].set(x);
g[y+1].set(y);
releaf|=op;
}
}
int len = releaf.size();
bitset<p> ks;
if(releaf.count()==2)
{
for(int i=1;i<=n;i++)
{
if(b[i].count()!=n)
{
lef |= b[i];
lef ^=releaf;
break;
}
}
int st = releaf.ff();
int ry = releaf.size();
int stt = releaf.fx(st);
cout<<st+1<<" "<<stt+1<<'\n';
for(int i=0;i<n;i++)
{
if(lef[i]) cout<<st+1<<" "<<i+1<<'\n';
else
{
if(i!=st&&i!=stt)
{
cout<<stt+1<<" "<<i+1<<'\n';
}
}
}
return 0;
}
for(int i=0;i<len;i++)
{
ks.reset();ks.set(i);
ks = ks&releaf;
if(ks.count()) a[++tit] = i+1;
}
bitset<p> aux;
aux = releaf;
aux.flip();
int opt = aux.ff();
int gi = -1;
for(;opt!=gi&&opt<n;opt = aux.fx(opt))
{
int minn = INF;
int lsq= 0 ;
for(int i=1;i<=n;i++)
{
int ikj = 0;
if(b[i][opt])
{
if(minn>(b[i]&releaf).count())
{
ikj =1;
ks = b[i]&releaf;
minn = ks.count();
vis[opt+1] = i;
}
}
}
for(int j=1;j<=tit;j++)
{
if(g[a[j]] == ks)
cout<<a[j]<<" "<<opt+1<<'\n';
}
gi = opt;
}
for(int i=1;i<=tit;i++)
{
g[a[i]][a[i]-1] = 0;
gi =- 1;
opt = g[a[i]].ff();
for(;opt!=gi&&opt<n;opt = g[a[i]].fx(opt))
{
if((!c[a[i]][opt+1])&&(!c[opt+1][a[i]]))
{
cout<<a[i]<<" "<<opt+1<<'\n';
c[a[i]][opt+1] = c[opt+1][a[i]] = 1;
}
gi = opt;
}
}
}
\(End\)
这是我的第一道黑题,也是第一道\(bitset\)优化构造题