1. 程式人生 > >【洛谷P4934】 禮物,拓撲排序

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