【洛谷P4934】 礼物,拓扑排序

题目大意:给你$n$个不重复的数,其值域为$[0,2^k)$,问你至少需要将这$n$个数拆成多少个集合,使得它们互相不是对方的子集,并输出方案。

数据范围:$n≤10^6$,$k≤20$。

$MD$我场上都想了啥。。。。

我们显然有一种$O(3^k)$的做法,对于数字$x$,我们枚举其子集,设当前枚举到的子集为$u$,我们连一条$u->x$的边,然后跑一个拓扑排序,即可确定至少需要划分为多少个集合(我场上根本没在想拓扑排序。。。。)

然后,这个显然会$TLE+MLE$。

然后我们发现,若存在$u,v,w,$满足$u$是$v$的子集,$v$是$w$的子集,那么这种情况下,从$w$连边向$u$,其实是多余的。故对于数字$x$,我们只需要连接$u->x$,其中$u$^$x=2^p$。那么边的数量就成功降低至$2^k$。

时间复杂度:$O(nk)$。

 1 #include<bits/stdc++.h>
 2 #define M 1100005
 3 using namespace std;
 4 struct edge{int u,next;}e[M*20]={0}; int head[M]={0},use=0;
 5 void add(int x,int y){use++;e[use].u=y;e[use].next=head[x];head[x]=use;}
 6 int n,k,id[M]={0};
 7 queue<int> q; int dfn[M]={0},in[M]={0};
 8 int main(){
 9     scanf("%d%d",&n,&k);
10     for(int i=1;i<=n;i++){
11         int x; scanf("%d",&x);
12         id[x]=i;
13     }
14     for(int i=0;i<(1<<k);i++){
15         for(int j=0;j<k;j++)
16         if((i&(1<<j))==0) add(i,i^(1<<j)),in[i^(1<<j)]++;
17     }
18     q.push(0);
19     while(!q.empty()){
20         int u=q.front(); q.pop();
21         if(id[u]) dfn[u]++;
22         for(int i=head[u];i;i=e[i].next){
23             in[e[i].u]--; 
24             dfn[e[i].u]=max(dfn[e[i].u],dfn[u]);
25             if(in[e[i].u]==0) q.push(e[i].u);
26         }
27     }
28     cout<<1<<endl;
29     cout<<dfn[(1<<k)-1]<<endl;
30     
31     for(int i=1;i<=dfn[(1<<k)-1];i++){
32         int res=0;
33         for(int j=0;j<(1<<k);j++) res+=(dfn[j]==i&&id[j]);
34         printf("%d ",res);
35         for(int j=0;j<(1<<k);j++) if(dfn[j]==i&&id[j]) printf("%d ",j);
36         printf("\n");
37     }
38 }

猜你喜欢

转载自www.cnblogs.com/xiefengze1/p/9826860.html
今日推荐