测试地址:礼物
做法: 本题需要用到Dilworth定理+优化建图+拓扑排序。
对位运算感觉比较敏锐的话,可以看出,
a
&
b
a\&b
a&b这个东西要么比
a
,
b
a,b
a,b都小,要么就说明
a
,
b
a,b
a,b中有一个在二进制位上是另一个数的子集。因为属于的关系是一种偏序关系,根据Dilworth定理,偏序反链最小覆盖等于最长偏序链,因此我们可以
O
(
n
2
)
O(n^2)
O(n2),或者用枚举子集
O
(
3
k
)
O(3^k)
O(3k)建图,然后就可以找最长链了。但很明显这不能解决这道题,因此我们需要找到优化建图的方法。
我们发现,如果对于每个点,只向所有比这个点二进制中正好少一个
1
1
1的点连边的话,和上面能达到的效果是一样的。事实上大部分优化建图都是这样,用等价的变换,把原来的很多条边变成很少部分边的路径集,减小建图和进行图上处理的复杂度。这样时间复杂度就是
O
(
k
2
k
)
O(k2^k)
O(k2k)的了。
还有一个问题,我们要求的并不是最长链,而是最小反链覆盖,这就需要在处理上下点功夫了。考虑拓扑排序,对于一开始的暴力建图法,我们只需要每次取出所有当前入度为
0
0
0的点作为一个集合就行了,注意为了避免产生影响,我们先把这些点取完再处理这些点对其他点入度的影响。用一个队列处理即可。但采用了优化建图法后,产生了一些实际上不存在的点,那么对于这些点,我们另开一个队列存储。删掉这些点的策略和上面的不同,策略是保证删完之后,不存在任何一个这样的点入度为
0
0
0,这样才不会使得本来能取为集合内点的点不能取。那么整理一下算法,每一次取集合,我们先删掉所有能删掉的实际上不存在的点,然后取当前入度为
0
0
0的点作为一个集合。这样我们就能解决这一题了,时间复杂度为
O
(
k
2
k
)
O(k2^k)
O(k2k)。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int n,k,q[2000010][2],tot[2]={0},anstot=0;
int head[2]={1,1},in[2000010]={0};
bool vis[2000010]={0};
vector<int> ans[30];
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
vis[x]=1;
}
for(int i=(1<<k)-1;i>=0;i--)
{
if (!in[i]) q[++tot[vis[i]]][vis[i]]=i;
for(int j=0;j<k;j++)
if (i&(1<<j)) in[i-(1<<j)]++;
}
while(head[0]<=tot[0]||head[1]<=tot[1])
{
while(head[0]<=tot[0])
{
int v=q[head[0]++][0];
for(int j=0;j<k;j++)
if (v&(1<<j))
{
in[v-(1<<j)]--;
if (!in[v-(1<<j)])
q[++tot[vis[v-(1<<j)]]][vis[v-(1<<j)]]=v-(1<<j);
}
}
if (head[1]>tot[1]) break;
anstot++;
while(head[1]<=tot[1])
{
int v=q[head[1]++][1];
ans[anstot].push_back(v);
}
for(int i=0;i<(int)ans[anstot].size();i++)
{
int v=ans[anstot][i];
for(int j=0;j<k;j++)
if (v&(1<<j))
{
in[v-(1<<j)]--;
if (!in[v-(1<<j)])
q[++tot[vis[v-(1<<j)]]][vis[v-(1<<j)]]=v-(1<<j);
}
}
}
printf("1\n%d\n",anstot);
for(int i=1;i<=anstot;i++)
{
printf("%d ",ans[i].size());
for(int j=0;j<(int)ans[i].size();j++)
printf("%d ",ans[i][j]);
printf("\n");
}
return 0;
}