【LuoguP4934】礼物(LGR-054)-Dilworth定理+优化建图+拓扑排序

测试地址:礼物
做法: 本题需要用到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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值