jzoj 3058【NOIP2012模拟10.26】雕塑TJ

题目描述

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

题解

解法一:容斥原理

注意到每列一个,每行一个,于是没有障碍时的方案数为PnnP_{n}^{n}Pnnn!n!n!,设为sum0sum_{0}sum0
在考虑有障碍时的情况,根据容斥原理,我们知道ansansans应是奇减偶加,所以可以用一个dfsdfsdfs枚举障碍,设sumisum_{i}sumi为同时放iii个障碍的位置的障碍的选择方案数,可得
ans=∑i=0n((−1)i∗sumi∗(n−i)!)ans=\sum_{i=0}^{n}((-1)^{i}*sum_{i}*(n-i)!)ans=i=0n((1)isumi(ni)!)
时间复杂度显然为O(m+2n)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;=10N&lt;=20,m&lt;=10N<=20,m<=10,立马想到了状压,于是非常迅速地打了一个极其简单的状压:
fi,Sf_{i,S}fi,S为枚举到第iii行,每列已放的状态为SSS的方案数,易得转移方程:
fj,S∪2j−1=∑fi,S   ∣i!=j,S⊆2i−1f_{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(n2∗2n)O(n^{2}*2^{n})O(n22n),这明显爆了好不?于是我们采用了一个简单粗暴的优化,我们判断S中111的个数,只在个数=j−1个数=j-1=j1时进行转移,SSS111的个数可以线性求出,即lowbitx=lowbitx−(x∩−x)+1lowbit_{x}=lowbit_{x-(x \cap -x)}+1lowbitx=lowbitx(xx)+1,然后就愉快地结束了。
时间复杂度约O(n∗2n)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、付费专栏及课程。

余额充值