[CSP-S模擬測試]:Lighthouse(哈密頓迴路+容斥)

題目背景

$Billions\ of\ lighthouses...stuck\ at\ the\ far\ end\ of\ the\ sky.$c++


題目描述

    平面有$n$個燈塔,初始時兩兩之間能夠相互交流;但因爲地形緣由,有$m$對燈塔之間沒法進行直接的交流。也就是一張徹底圖缺乏了$m$條邊。
    $River$想把這$n$個燈塔連成一個環,使得$n$個等他都在環上,而且環上相鄰的兩個燈塔能進行直接交流。$River$想知道這樣作的方案數是多少,兩種方案被認爲是不一樣的,當且僅當有兩個燈塔$u,v$,他們在一種方案中在環上相鄰,而在另外一種方案中相反。
    答案可能很大,你只須要輸出對${10}^9+7$取模的結果。
spa


輸入格式

第一行兩個整數$n,m$。
接下來$m$行,每行描述一條缺乏的邊。
blog


輸出格式

一行一個整數表示答案。it


樣例

樣例輸入1:io

4 1
1 2
class

樣例輸出1:map

1im

樣例輸入2:數據

10 3
1 9
3 8
2 7
img

樣例輸出2:

87840


數據範圍與提示

樣例$1$解釋:

惟一的方案是(1,3,2,4)依次連成環。

數據範圍:

對於全部數據,有$3\leqslant n\leqslant {10}^7,0\leqslant m\leqslant \min(20,\frac{n(n-1)}{2})$。輸入的邊中沒有重邊。


題解

    題目實際上球的就是哈密頓迴路的數量。因爲$m$很小,考慮容斥。
    枚舉刪除的邊的某個子集$S$,設$f_S$表示有多少條哈密頓迴路至少包含$S$集合中的邊,答案就是$\sum_S(-1)^{|S|}\times f_S$。
    怎麼算$f_S$呢?首先判掉$f_S=0$的狀況,這包含如下兩種狀況:
        $\alpha.$僅考慮$S$中的邊時,某個點的度數大於$2$。
        $\beta.$出現了環,而且這個環的大小不爲$n$。
    特判掉$S$自己就是一個哈密頓迴路的狀況;假設$S$中的邊構成了$k$條鏈,那麼$f_S=s{k-1}\times (n-|S|-1)!$。
    證實:考慮將一條鏈當作一個點,那麼總共有$n-|S|$個點,其環排列方案數爲$(n-|S|-1)!$;每條鏈均可以翻轉,所以乘上$2^k$;又因爲一條哈密頓迴路對應了兩個環排列(正反兩個方向),還要除以$2$。

時間複雜度:$\Theta(2^m\times m)$。

指望得分:$100$分。

實際得分:$100$分。


代碼時刻

#include<bits/stdc++.h>
using namespace std;
int n,m;
long long jc[10000001];
int fa[10000001];
pair<int,int> pos[10000001];
int tot;
long long ans;
int cnt[10000001],vis[10000001],g[10000001];
int que[10000001];
map<pair<int,int>,bool> h;
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
long long qpow(long long x,long long y)
{long long res=1;while(y){if(y&1)res=res*x%1000000007;x=x*x%1000000007;y>>=1;}return res;}
long long solve(int x)
{
	int sum=0,k=que[0]=0;
	bool flag=0;
	for(int i=1;i<=tot;i++)
		if((x>>(i-1))&1)
		{
			que[++que[0]]=i;
			fa[pos[i].first]=pos[i].first;
			fa[pos[i].second]=pos[i].second;
			vis[pos[i].first]=vis[pos[i].second]=g[pos[i].first]=g[pos[i].second]=0;
		}
	for(int i=1;i<=que[0];i++)
	{
		if(!vis[pos[que[i]].first])sum++;
		if(!vis[pos[que[i]].second])sum++;
		if(vis[pos[que[i]].first]==2||vis[pos[que[i]].second]==2)return 0LL;
		vis[pos[que[i]].first]++;
		vis[pos[que[i]].second]++;
	}
	for(int i=1,u,v;i<=que[0];i++)
	{
		if((u=find(pos[que[i]].first))==(v=find(pos[que[i]].second)))flag=1;
		fa[u]=v;
	}
	for(int i=1,u;i<=que[0];i++)
		if(!g[u=find(pos[que[i]].first)]){g[u]=1;k++;}
	if(flag&&(cnt[x]!=n||k>1))return 0LL;
	long long res=jc[n-sum+k-1]*qpow(2,k)%1000000007*qpow(2,1000000005)%1000000007;
	return (cnt[x]&1)?-res:res;
}
int main()
{
	jc[0]=1;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		if(x==y||h[make_pair(x,y)])continue;
		h[make_pair(x,y)]=1;
		pos[++tot]=make_pair(x,y);
	}
	for(int i=1;i<=n;i++)jc[i]=1LL*i*jc[i-1]%1000000007;
	for(int i=0;i<(1<<tot);i++)cnt[i]=cnt[i>>1]+(i&1);
	for(int i=0;i<(1<<tot);i++)ans=(ans+solve(i))%1000000007;
	printf("%lld",(ans+1000000007)%1000000007);
	return 0;
}

rp++

相關文章
相關標籤/搜索