jzoj 3058【NOIP2012模拟10.26】雕塑TJ

题目描述

在一个 N 2 N^{2} N2并有 M M M个障碍的棋盘内每一行每一列都放置一个雕塑,问有多少种放置方案。
时限: 1000 ms 空限: 131072 KB

题解

解法一:容斥原理

注意到每列一个,每行一个,于是没有障碍时的方案数为 P n n P_{n}^{n} Pnn n ! n! n!,设为 s u m 0 sum_{0} sum0
在考虑有障碍时的情况,根据容斥原理,我们知道 a n s ans ans应是奇减偶加,所以可以用一个 d f s dfs dfs枚举障碍,设 s u m i sum_{i} sumi为同时放 i i i个障碍的位置的障碍的选择方案数,可得
a n s = ∑ i = 0 n ( ( − 1 ) i ∗ s u m i ∗ ( n − i ) ! ) ans=\sum_{i=0}^{n}((-1)^{i}*sum_{i}*(n-i)!) ans=i=0n((1)isumi(ni)!)
时间复杂度显然为 O ( m + 2 n ) O(m+2^{n}) O(m+2n)
于是就愉快地解决了:)注意重复的障碍!

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ll long long
using namespace std;
int n,m,a[20],b[20],tot;
ll ans,t,sum,jc[30];
bool bz1[30],bz2[30],bz[30][30];
void dfs(int x,int cnt,int s){
	if(x>m){
		if(cnt==s)sum++;
		return;
	}
	dfs(x+1,cnt,s);
	if(!bz1[a[x]]&&!bz2[b[x]]){
		bz1[a[x]]=1;
		bz2[b[x]]=1;
		dfs(x+1,cnt+1,s);
		bz1[a[x]]=0;
		bz2[b[x]]=0;
	}
}
int main(){
	scanf("%d%d",&n,&m);
	fo(i,1,m){
		tot++;
		scanf("%d%d",&a[tot],&b[tot]);
		if(bz[a[tot]][b[tot]])tot--;
		bz[a[tot]][b[tot]]=1;
	}
	jc[1]=jc[0]=1;
	fo(i,2,n){
		jc[i]=jc[i-1]*i;
	}
	ans=jc[n];
	t=1;
	fo(i,1,tot){
		sum=0;
		dfs(1,0,i);
		t=-t;
		ans+=t*sum*jc[n-i];
	}
	printf("%lld\n",ans);
	
	return 0;
}

解法二:状压DP

一眼看到数据范围 N &lt; = 20 , m &lt; = 10 N&lt;=20,m&lt;=10 N<=20,m<=10,立马想到了状压,于是非常迅速地打了一个极其简单的状压:
f i , S f_{i,S} fi,S为枚举到第 i i i行,每列已放的状态为 S S S的方案数,易得转移方程:
f j , S ∪ 2 j − 1 = ∑ f i , S     ∣ i ! = j , S ⊆ 2 i − 1 f_{j,S \cup 2^{j-1}}=\sum f_{i,S} \ \ \ |i!=j,S \subseteq 2^{i-1} fj,S2j1=fi,S   i!=j,S2i1
然后我们发现了一个不对的地方——时间复杂度 O ( n 2 ∗ 2 n ) O(n^{2}*2^{n}) O(n22n),这明显爆了好不?于是我们采用了一个简单粗暴的优化,我们判断S中 1 1 1的个数,只在 个 数 = j − 1 个数=j-1 =j1时进行转移, S S S 1 1 1的个数可以线性求出,即 l o w b i t x = l o w b i t x − ( x ∩ − x ) + 1 lowbit_{x}=lowbit_{x-(x \cap -x)}+1 lowbitx=lowbitx(xx)+1,然后就愉快地结束了。
时间复杂度约 O ( n ∗ 2 n ) O(n*2^{n}) O(n2n)

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define INF 1000000007
using namespace std;
int n,m,x,y,t;
long long f[3][1048580];
int g[1048580];
bool bz[30][30];
int main(){
	scanf("%d%d",&n,&m);
	fo(i,1,m){
		scanf("%d%d",&x,&y);
		bz[x][y]=1;
	}
	g[0]=0;
	fo(S,1,(1<<n)-1){
		g[S]=g[S-(S&(-S))]+1;
	}
	f[0][0]=1;
	t=1;
	fo(i,0,n-1){
		t=1-t;
		fo(S,0,(1<<n)-1){
			if(g[S]!=i)continue;
			fo(j,1,n){
				if((1<<j-1)&S)continue;
				if(bz[i+1][j])continue;
				f[1-t][S|(1<<(j-1))]+=f[t][S];
			}
		}
	}
	printf("%lld\n",f[n%2][(1<<n)-1]);
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值