LUOGU P5061 祕密任務(揹包+二分圖染色)
阿新 • • 發佈:2018-12-07
解題思路
\(orz\)出題人的神仙做法。本蒟蒻看不懂,就水個求補圖再二分圖染色的方法來\(%1%\)出題人。
首先我們對圖中\(m\)個關係連邊,發現這樣是沒法做的,因為我們最後要關注的是誰和誰不能在一起,這個限制是比較大的。所以我們考慮建一個補圖,就是把原來沒有的邊加邊,原來存在的邊斷掉。這樣\(a\)和\(b\)之間有邊就代表\(a\)與\(b\)不能屬於一個集合,這樣就可能形成了若干個圖。首先考慮判合法,因為一共只有兩個集合,而每個人都必須放到集合裡,關係還可以抽象成一張無向圖,自然可以想到二分圖染色了。我們只需要遍歷每一個聯通塊,然後進行黑白染色判是否合法,只要有一個聯通塊不合法,那麼也就\(GG\)
然後考慮算答案,判完合法之後,我們就可以知道一個了聯通塊中黑色和白色的不能屬於一個集合,剩下的可以任意搭配,所以做一個揹包就行了,把每個聯通塊黑色白色的個數記下來。設\(f[i]\)表示一個集合有\(i\)個人是否成立,轉移的時候就模仿\(0/1\)揹包,就是看每一個聯通塊是選黑色進去還是選白色進去。做完揹包後一個人數合法僅當\(f[i]=f[n-i]=true\)。這樣第一問和第二問的答案就統計出來了,對於第三問的答案,然後\(n^2\)列舉一下每對,如果兩個人屬於同一個聯通塊但顏色不相同,並且兩個人在補圖裡沒邊,就使\(ans3++\),這個也比較好理解,具體實現看程式碼。
程式碼
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int MAXN = 2505; const int MOD = 1e9+7; typedef long long LL; inline int rd(){ int x=0,f=1;char ch=getchar(); while(!isdigit(ch)) f=ch=='-'?0:1,ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar(); return f?x:-x; } int n,m,a[MAXN][MAXN],tot,num,cnt1,cnt2,ans1,ans2,ans3; int w[MAXN][2],f[MAXN],now[MAXN],col[MAXN],Min; bool flag; void dfs(int x,int c){ col[x]=c;now[++tot]=x;if(c==1) cnt1++;else cnt2++; for(int i=1;i<=n;i++) if(a[i][x]){ if(col[i]==col[x]) {flag=1;return;} if(!col[i]) dfs(i,3-c); } } inline int fast_pow(int x,int y){ int ret=1; for(;y;y>>=1){ if(y&1) ret=(LL)ret*x%MOD; x=(LL)x*x%MOD; } return ret; } int main(){ int x,y;n=rd(),m=rd(); for(int i=1;i<=m;i++){ x=rd(),y=rd(); a[x][y]=a[y][x]=1; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i!=j) a[i][j]^=1; for(int i=1;i<=n;i++) if(!col[i]){ cnt1=cnt2=tot=0;memset(now,0,sizeof(now)); dfs(i,1);if(flag) break; for(int j=1;j<=tot;j++) for(int k=j+1;k<=tot;k++) if(col[now[j]]!=col[now[k]] && !a[now[j]][now[k]]) ans3++; w[++num][0]=cnt1;w[num][1]=cnt2; }f[0]=1; for(int i=1;i<=num;i++){ Min=min(w[i][0],w[i][1]); for(int j=n;j>=Min;j--){ if(j>=w[i][0]) f[j]|=f[j-w[i][0]]; if(j>=w[i][1]) f[j]|=f[j-w[i][1]]; } } for(int i=0;i<=n/2;i++){ if(!f[i] || !f[n-i]) continue; ans1++;ans2=i; } if(flag) puts("-1"),ans3=m; //注意一下這裡,如果沒有方案的話自然$m$對可以合作的人都無法在一個集合裡 else printf("%d %d\n",ans1,(fast_pow(2,n-ans2)-fast_pow(2,ans2)+MOD)%MOD); printf("%d\n",ans3); return 0; }