【洛谷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 }