题目描述
在一个
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)i∗sumi∗(n−i)!)
时间复杂度显然为
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
<
=
20
,
m
<
=
10
N<=20,m<=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,S∪2j−1=∑fi,S ∣i!=j,S⊆2i−1
然后我们发现了一个不对的地方——时间复杂度
O
(
n
2
∗
2
n
)
O(n^{2}*2^{n})
O(n2∗2n),这明显爆了好不?于是我们采用了一个简单粗暴的优化,我们判断S中
1
1
1的个数,只在
个
数
=
j
−
1
个数=j-1
个数=j−1时进行转移,
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−(x∩−x)+1,然后就愉快地结束了。
时间复杂度约
O
(
n
∗
2
n
)
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;
}