状压DP
状压
一般看到数据范围是16左右的时候想状压dp
解决两类问题:
集合问题
棋盘问题
O(\(n^4\))
for(int s=0;s<(1<<n);s++)
for(int t=0;t<(1<<n);t++)
if(t&s==t)
O(\(n^3\))
for(int s=0;s<(1<<n);s++)
for(int t=s;t>0;t=(t-1)&s)
方格取数
#include <bits/stdc++.h>
using namespace std;
int a[19][1 << 17];
int f[19][1<<17];
int tot[1<<17];
int calc(int i,int x){
int cnt = 1,res = 0;
while(x){
if(x & 1) res += a[i][cnt];
x /= 2;cnt++;
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
int n;
while(cin >> n){
int cnt = 0,ans = 0;
for(int i = 0;i < (1 << n);i++) if((i & (i >> 1)) == 0) tot[++cnt] = i;
for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) cin >> a[i][j];
for(int i = 1;i <= n;i++){
for(int k = 1;k <= cnt;k++){
int val = calc(i,tot[k]);
for(int j = 1;j <= cnt;j++){
if((tot[j] & tot[k]) == 0){
f[i][k] = max(f[i][k],f[i - 1][j] + val);
}
}
}
}
for(int j = 1;j <= cnt;j++) ans = max(ans,f[n][j]);
cout << ans << endl;
}
return 0;
}
例1:互不侵犯
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
( 1 <=N <=9, 0 <= K <= N * N)
f{i}{j}{k}第 i 行、此行放什么状态(用 j 表示)、包括这一行 已经使用了的国王数 k 。
我们预先处理出每一个状态(sit[x])其中包含二进制下1的个数,以及此状态下这一行放的国王个数(gs[x])
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
LL f[10][1<<10][100],sum;
int n,k,cnt;
int pre[1<<10],num[1<<10];
int lowbit(int x){//处理出每行的国王数
int ans=0;
while(x){
ans+=(x&1);
x>>=1;
}
return num[cnt]=ans;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=0;i<(1<<n);i++){//预处理第一行
if(!(i&(i<<1))&&!(i&(i>>1)))
pre[++cnt]=i,f[1][cnt][lowbit(i)]=1;
}
for(int i=2;i<=n;i++){
for(int j=1;j<=cnt;j++){//枚举当前行
int x=pre[j];
for(int s=1;s<=cnt;s++){//枚举上一行
int y=pre[s];
if((x&y) || (x&(y<<1)) || (x&(y>>1)))continue;
for(int u=0;num[j]+u<=k;u++)
f[i][j][num[j]+u]+=f[i-1][s][u];
}
}
}
for(int i=1;i<=cnt;i++)
sum+=f[n][i][k];
printf("%lld",sum);
return 0;
}
例2:Corn Fields G
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int mod=100000000;
int n,m,ans=0;
int a[15][15];
int G[13];
int f[13][1<<17];
int h[1<<17];//判断合法
int main(){
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++){
scanf("%d",&a[i][j]);
G[i]=(G[i]<<1)+a[i][j];
}
for(int i=0;i<(1<<n);i++)
h[i]=(!((i<<1)&i) && !((i>>1)&i));
f[0][0]=1;
for(int i=1;i<=m;i++)
for(int j=0;j<(1<<n);j++)
if(h[j]&&((j&G[i])==j))
for(int k=0;k<(1<<n);k++)
if(!(k&j))f[i][j]=(f[i][j]+f[i-1][k])%mod;
for(int i=0;i<(1<<n);i++)
ans=(ans+f[m][i])%mod;
printf("%d\n",ans);
return 0;
}
例3:[USACO08NOV] Mixed Up Cows G
有N头奶牛(4 <= N <= 16),第i头奶牛的编号是Si,每头奶牛的编号都是唯一的。她们在挤奶的时候一定要排成混乱的队伍。在一只混乱的队 伍中,相邻奶牛的编号之差均超过K。比如当K = 1时,1, 3, 5, 2, 6, 4就是一支混乱的队伍, 而1, 3, 6, 5, 2, 4不是,因为6和5只差1。请数一数,有多少种队形是混乱的呢?
解: f {i}{j}表示以第i只奶牛为结尾的状态为j的队伍混乱的方案数是多少
然后枚举最后一头牛
#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
#define LL long long
LL n,m,a[18],f[18][1<<18],ans;
int main(){
scanf("%lld%lld",&n,&m);
for(LL i=1;i<=n;i++)scanf("%lld",&a[i]);
for(LL i=1;i<=n;i++)
f[i][1<<(n-i)]=1;
for(LL j=1;j<(1<<n);j++)//注意:先枚举状态
for(LL i=1;i<=n;i++){
if(!(j&(1<<(n-i))))continue;
for(LL k=1;k<=n;k++){
if(j&(1<<(n-k)))continue;
if(abs(a[k]-a[i])>m)f[k][(1<<(n-k))|j]+=f[i][j];
}
}
for(LL i=1;i<=n;i++)
ans+=f[i][(1<<n)-1];
printf("%lld",ans);
return 0;
}
例4:题目
给出n个物品,体积为w[i],现把其分成若干组,要求每组总体积<=W,问最小分组。(n<=18)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
int n,m,w[20],f[1<<19],g[1<<19];
//f[i]就代表当前状态为i时最小组数 ,g[]来记录当前状态下剩余的体积
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
memset(f,63,sizeof(f));
f[0]=1;
g[0]=m;
for(int i=0;i<(1<<n);i++){
for(int j=1;j<=n;j++){
if(i&(1<<(j-1)))continue;
if(g[i]>=w[j]){
if(f[i|(1<<(j-1))]>f[i]){
f[i|(1<<(j-1))]=f[i];g[i|(1<<(j-1))]=g[i]-w[j];
}
else if(f[i|(1<<(j-1))]==f[i])
g[i|(1<<(j-1))]=max(g[i|(1<<(j-1))],g[i]-w[j]);
}else if(g[i]<w[j]){
if(f[i|(1<<(j-1))]>f[i]+1){
f[i|(1<<(j-1))]=f[i]+1;
g[i|(1<<(j-1))]=m-w[j];
}
else if(f[i|(1<<(j-1))]==f[i]+1)
g[i|(1<<(j-1))]=max(g[i|(1<<(j-1))],m-w[j]);
}
}
}
printf("%d",f[(1<<n)-1]);
return 0;
}
例5:排列
给一个数字串 s和正整数 d, 统计 s 有多少种不同的排列能被 d 整除(可以有前导 0)。
100% 的数据满足:s 的长度不超过 10,1≤d≤1000,1≤T≤15。(多组数据)
解:
状压 f { i }{ j }第一维表示状态,第二维表示余数
/*
最关键:f[i|(1<<(j-1))][(k*10+a[j])%d]+=f[i][k];
细节:如1223序列在状态枚举时
0101 和 0011统计了两次,但其实都是一种排序(排序重复不计)所以用vis来去重(每次重置0)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
bool vis[15];
int T,d,f[1<<11][1005],a[15];
char s[15];
int main(){
scanf("%d",&T);
while(T--){
memset(f,0,sizeof(f));
scanf("%s%d",s+1,&d);
int n=strlen(s+1);
for(int i=1;i<=n;i++)a[i]=s[i]-'0';
f[0][0]=1;
for(int i=0;i<(1<<n);i++){
memset(vis,0,sizeof(vis));
for(int j=1;j<=n;j++){
if(i&(1<<(j-1)))continue;
if(!vis[a[j]]){
vis[a[j]]=1;
for(int k=0;k<d;k++)
f[i|(1<<(j-1))][(k*10+a[j])%d]+=f[i][k];
}
}
}
printf("%d\n",f[(1<<n)-1][0]);
}
return 0;
}
例6: 奖励关
#include <cstdio>
#include <iostream>
using namespace std;
int k,n,s[1<<16],p[20],ss;
double f[105][1<<16];
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int main() {
k=read();n=read();
for(int i=1;i<=n;i++) {
p[i]=read();
while(1) {
ss=read();
if(ss==0)break;
s[i]|=(1<<(ss-1));
}
}
for(int i=k;i>=1;i--){
for(int state=0;state<(1<<n);state++) {
for(int j=1;j<=n;j++) {
if((s[j]&state)!=s[j]) f[i][state]+=f[i+1][state]; //不满足
else f[i][state]+=max(f[i+1][state],f[i+1][state|1<<(j-1)]+p[j]);
}
f[i][state]/=n;
}
}
printf("%.6lf\n",f[1][0]);
return 0;
}
例7:奇怪的道路
好吧看到一开始想是状压,然后看到n<=30放弃了,,,反正考试5选3就去调别的了。。。
好了看题解说正解
#include<iostream>
#include<cstdio>
using namespace std;
const int Mod = 1000000007;
int n,m,k;
long long f[35][35][1<<10][10];
signed main(){
scanf("%d%d%d",&n,&m,&k);
f[1][0][0][0]=1;
for(int i=1;i<n;i++){
for(int j=0;j<=m;j++){
for(int s=0;s<(1<<(k+1));s++){
for(int p=0;p<k;p++){
if(!f[i][j][s][p]) continue;
(f[i][j][s][p+1]+=f[i][j][s][p])%=Mod;//不连边
if (j<m && i-k+1+p>0) (f[i][j+1][s^(1<<k)^(1<<p)][p]+=f[i][j][s][p])%=Mod;//连边,影响k和p奇偶性
if ((s&1)==0) (f[i+1][j][s>>1][0]+=f[i][j][s][k])%=Mod;//当前点连的是奇数条边,下一个点向当前连边
}
}
}
}
printf("%lld",f[n][m][0][0]);
return 0;
}