题面
分析
状压DP模版
由于某一行的状态与上一行有关,且有国王个数限制
于是设
f
[
i
]
[
s
]
[
k
]
f[i][s][k]
f[i][s][k]为第i行状态为s,用了k个国王时的可能性
于是有
f
[
i
]
[
s
]
[
k
]
=
∑
f
[
i
−
1
]
[
s
2
]
[
k
−
w
[
s
]
]
f[i][s][k]=\sum f[i-1][s2][k-w[s]]
f[i][s][k]=∑f[i−1][s2][k−w[s]]
(s2指第i-1行的与s相互合法的且自身合法的状态,w[s]指s所用的国王数)
于是代码就好写了
code
#include<bits/stdc++.h>
using namespace std;
#define loop(i,start,end) for(register int i=start;i<=end;i++)
#define anti_loop(i,start,end) for(register int i=start;i>=end;i--)
#define clean(arry,num); memset(arry,num,sizeof(arry));
#define legal_line(st) ((!((st<<1)&st))?true:false)
#define legal_between(a,b) ( ( (((a<<1)&b)==0) && (((a>>1)&b)==0) &&((a&b)==0))?true:false)
#define max(a,b) ((a>b)?a:b)
#define ll long long
const int maxn=15;
const int maxk=maxn*maxn;
const int maxs=1024;
ll f[maxn][maxs][maxk];
int l[maxs];
int n_used[maxs];
int n,k,n_l=0;
int countOne(int a)
{
int ans=0;int st=a;
while(st>0){ans+=(!(st&1))?0:1;st>>=1;}
return ans;
}//ok
int main()
{
//freopen("datain.txt","r",stdin);
scanf("%d%d",&n,&k);//n x n
loop(i,0,(1<<n)-1)
if(legal_line(i))
{
l[n_l]=i;
n_used[n_l]=countOne(i);
n_l++;
}
clean(f,0);
loop(s,0,n_l-1)f[1][l[s]][n_used[s]]=1;
loop(i,2,n)
{
loop(s,0,n_l-1)//dang qian hang de sitiuation
{
loop(j,n_used[s],k)//
{
loop(sf,0,n_l-1)
{
if(legal_between(l[s],l[sf])&&n_used[sf]<=j-n_used[s])
{
f[i][l[s]][j]+=f[i-1][l[sf]][j-n_used[s]];
}
}
}
}
}
ll ans=0;
loop(s,0,n_l-1)ans+=f[n][l[s]][k];
printf("%lld",ans);
return 0;
}
学到的东西
1.状压DP里的骚操作
位运算
常用的以位运算实现的功能
- 集合取并(Set union)
A | B - 集合取交(Set intersection)
A & B - 集合相减(Set subtraction)
A & ~B - 集合取反(Set negation)
ALL_BITS ^ A - 置位(Set bit)
A |= 1 << bit - 清位(Clear bit)
A &= ~(1 << bit) - 测位(Test bit)
(A & 1 << bit) != 0
(A >> bit & 1) != 0 - 取最后一个非0位(Extracting every last bit)
A & -A
A & ~(A-1) - 统计非0位(Counting out the bits)
For (; A; A -= A & -A) ++cnt; - 取所有子集(All the subsets)
X = A
While (X) X = (X - 1) & A - 判断是否有相邻的1
(A & A>>1) == 0 - 交换两整数
a ^= b, b ^= a, a ^= b
2.DP题的“规律”
- 找状态
- 得方程
- 代码实现
感觉自己在放屁