题目描述
在一个N2N^{2}N2并有MMM个障碍的棋盘内每一行每一列都放置一个雕塑,问有多少种放置方案。
时限: 1000 ms 空限: 131072 KB
题解
解法一:容斥原理
注意到每列一个,每行一个,于是没有障碍时的方案数为PnnP_{n}^{n}Pnn即n!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)i∗sumi∗(n−i)!)
时间复杂度显然为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<=20,m<=10N<=20,m<=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,S∪2j−1=∑fi,S ∣i!=j,S⊆2i−1
然后我们发现了一个不对的地方——时间复杂度O(n2∗2n)O(n^{2}*2^{n})O(n2∗2n),这明显爆了好不?于是我们采用了一个简单粗暴的优化,我们判断S中111的个数,只在个数=j−1个数=j-1个数=j−1时进行转移,SSS中111的个数可以线性求出,即lowbitx=lowbitx−(x∩−x)+1lowbit_{x}=lowbit_{x-(x \cap -x)}+1lowbitx=lowbitx−(x∩−x)+1,然后就愉快地结束了。
时间复杂度约O(n∗2n)O(n*2^{n})O(n∗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 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;
}