状压DP总结

总结

状压DP就是将一个状态压缩为一个整数(通常为二进制数),就可以在更为方便地进行状态转移的同时,达到节约空间的目的。

题型一、冲突问题

1.1 特殊方格棋盘

题目描述


分析

我们设f[i][j]为当前的状态为\(i\),放置到第\(j\)行时的方案数
但是,因为我们要在\(n \times n\)的棋盘上放置\(n\)个车,所以,我们必定要在每一行都放置一个车
因此,我们可以把第二维省略掉
题目还要求有一些格点不能放车,所以我们要提前把每一行可以放置的位置枚举一下

for(ll i=1;i<=n;i++){
   vis[i]=mmax;
}
for(ll i=1;i<=m;i++){
   ll aa,bb;
   scanf("%lld%lld",&aa,&bb);
   vis[aa]-=(1<<(bb-1));
}

下面就是状态转移方程,如果上一行的状态和当前行的状态不冲突
那么当前行的方案数就要加上上一行的方案数
注意要把\(f[0][0]\)初始化为\(0\)

for(ll i=0;i<=mmax;i++){
    ll now=solve(i);//枚举当前是第几行
    ll can=i&(vis[now]);//枚举当前合法的状态
    for(ll j=1;j<=n;j++){
        if((can&(1<<(j-1)))==0) continue;//如果当前状态不包括j,直接continue掉
        ll las=i^(1<<(j-1));      //枚举上一行状态
        f[i]+=f[las];
    }
}

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=23;
ll f[1<<maxn],vis[maxn];
ll n,m;
ll solve(ll xx){
    ll ans=0;
    for(ll i=1;i<=n;i++){
        if(xx&(1<<(i-1))) ans++;
    }
    return ans;
}
int main(){
    scanf("%lld%lld",&n,&m);
    ll mmax=(1<<n)-1;
    for(ll i=1;i<=n;i++){
        vis[i]=mmax;
    }
    for(ll i=1;i<=m;i++){
        ll aa,bb;
        scanf("%lld%lld",&aa,&bb);
        vis[aa]-=(1<<(bb-1));
    }
    f[0]=1;
    for(ll i=0;i<=mmax;i++){
        ll now=solve(i);
        ll can=i&(vis[now]);
        for(ll j=1;j<=n;j++){
            if((can&(1<<(j-1)))==0) continue;
            ll las=i^(1<<(j-1));
            f[i]+=f[las];
        }
    }
    printf("%lld\n",f[mmax]);
    return 0;
}

1.2 互不侵犯

题目描述


分析

思路和上一道题一样,我们设\(f[i][j][k]\)为当前的行数为\(i\)状态为\(j\)并且放了\(k\)个国王时的方案数
因为第2行的状态要由第1行转移过来,所以我们要提前把第1行的状态处理一下

for(ll i=0;i<=mmax;i++){
    if(i&(i<<1) || i&(i>>1)) continue;
    f[1][i][solve(i)]=1;
}

在我们转移状态的时候,要注意当前行的状态不能冲突,也不能和上一行的状态冲突

    for(ll i=2;i<=n;i++){//枚举行数
        for(ll j=0;j<=mmax;j++){//枚举当前行的状态
            ll now=solve(j);
            if(j&(j<<1) || j&(j>>1)) continue;//当前行的状态不能冲突
            for(ll k=0;k<=mmax;k++){//枚举上一行的状态
                if(k&j || k&(j<<1) || k&(j>>1)) continue;//上一行的状态不能自己冲突,也不能和当前行的状态冲突
                for(ll c=0;c+now<=m;c++){
                    f[i][j][c+now]+=f[i-1][k][c];
                }//枚举放置国王的个数
            }
        }
    }

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=12;
ll f[maxn][1<<maxn][maxn*maxn];
ll n,m;
ll solve(ll xx){
    ll ans=0;
    for(ll i=1;i<=n;i++){
        if(xx&(1<<(i-1))) ans++;
    }
    return ans;
}
int main(){
    scanf("%lld%lld",&n,&m);
    ll mmax=(1<<n)-1;
    for(ll i=0;i<=mmax;i++){
        if(i&(i<<1) || i&(i>>1)) continue;
        f[1][i][solve(i)]=1;
    }
    for(ll i=2;i<=n;i++){
        for(ll j=0;j<=mmax;j++){
            ll now=solve(j);
            if(j&(j<<1) || j&(j>>1)) continue;
            for(ll k=0;k<=mmax;k++){
                if(k&j || k&(j<<1) || k&(j>>1)) continue;
                for(ll c=0;c+now<=m;c++){
                    f[i][j][c+now]+=f[i-1][k][c];
                }
            }
        }
    }
    ll ans=0;
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<=mmax;j++){
            ans+=f[i][j][m];
        }
    }
    printf("%lld\n",ans);
    return 0;
}

1.3 炮兵阵地

题目描述



分析

\(m\)的范围很小,所以我们考虑状压\(m\)
因为一个点会影响三行的状态,所以我们定义\(f[i][j][k]\)为当前枚举到第\(i\)行当前行的状态为\(j\)上一行的状态为\(k\)时的方案数
其他的和上一题一模一样
因为这一道题卡空间和时间,所以我们需要把合法的状态预处理出来

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=11;
typedef long long ll;
ll f[maxn*10][65][65],a[maxn*10],mmax;
char c[maxn*10][maxn];
ll zt[65],num[65];
ll n,m;
ll solve(ll xx){
    ll ans=0;
    for(ll i=0;i<m;i++){
        if(xx&(1<<i)) ans++;
    }
    return ans;
}
ll cnt=0;
void chuli(){
    for(ll i=0;i<mmax;i++){
        if(i&(i<<1) || i&(i<<2)) continue;
        cnt++;
        zt[cnt]=i;
        num[cnt]=solve(i);
    }
}
int main(){
    scanf("%lld%lld",&n,&m);
    for(ll i=1;i<=n;i++){
        scanf("%s",c[i]+1);
    }
    mmax=1<<m;
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<=m;j++){
            if(c[i][j]=='P'){
                a[i]+=(1<<(j-1));
            }
        }
    }
    chuli();
    ll ans=-1;
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<=cnt;j++){
            if(zt[j]!=(zt[j]&a[i])) continue;
            if(i==1){
                f[1][j][0]=max(f[i][j][0],num[j]);
                ans=max(ans,f[1][j][0]);
            }
            else if(i==2){
                for(ll k=1;k<=cnt;k++){
                    if(zt[k]!=(zt[k]&a[1])) continue;
                    if(zt[j]&zt[k]) continue;
                    f[2][j][k]=max(f[2][j][k],f[1][k][0]+num[j]);
                    ans=max(ans,f[2][j][k]);
                }
            } else {
                for(ll k=1;k<=cnt;k++){
                    if(zt[k]!=(zt[k]&a[i-1])) continue;
                    if(zt[k]&zt[j]) continue;
                    for(ll l=1;l<=cnt;l++){
                        if(zt[l]!=(zt[l]&a[i-2])) continue;
                        if(zt[l]&zt[j] || zt[l]&zt[k]) continue;
                        f[i][j][k]=max(f[i][j][k],f[i-1][k][l]+num[j]);
                        ans=max(ans,f[i][j][k]);
                    }
                }
            }
        }
    }
    printf("%lld\n",ans);
    return 0;
}

1.4 P1879 [USACO06NOV]Corn Fields G

题目描述

Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; 1 ≤ N ≤ 12) square parcels. He wants to grow some yummy corn for the cows on a number of squares. Regrettably, some of the squares are infertile and can't be planted. Canny FJ knows that the cows dislike eating close to each other, so when choosing which squares to plant, he avoids choosing squares that are adjacent; no two chosen squares share an edge. He has not yet made the final choice as to which squares to plant.

Being a very open-minded man, Farmer John wants to consider all possible options for how to choose the squares for planting. He is so open-minded that he considers choosing no squares as a valid option! Please help Farmer John determine the number of ways he can choose the squares to plant.

农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。

遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。

John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)

输入格式

第一行:两个整数M和N,用空格隔开。

第2到第M+1行:每行包含N个用空格隔开的整数,描述了每块土地的状态。第i+1行描述了第i行的土地,所有整数均为0或1,是1的话,表示这块土地足够肥沃,0则表示这块土地不适合种草。
输出格式

一个整数,即牧场分配总方案数除以100,000,000的余数。

输入输出样例

输入 #1

2 3
1 1 1
0 1 0

输出 #1

9

分析

一样的套路,一样的方程定义,一样的状态转移

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=15;
typedef long long ll;
ll f[maxn][1<<maxn],zt[maxn];
int main(){
    int m,n;
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            int aa;
            scanf("%d",&aa);
            if(aa==1) zt[i]+=(1<<(j-1));
        }
    }  
    int mmax=(1<<n)-1;
    f[1][0]=1;
    for(int i=0;i<=mmax;i++){
        if((i&zt[1])!=i || i&(i<<1) || i&(i>>1)) continue;
        f[1][i]=1;
    }
    for(int i=2;i<=m;i++){
        for(int j=0;j<=mmax;j++){
            if((j&zt[i])!=j) continue;
            if(j&(j<<1) || j&(j>>1)) continue;
            for(int k=0;k<=mmax;k++){
                if((k&zt[i-1])!=k) continue;
                if(k&j ||  k&(k<<1) || k&(k>>1)) continue;
                f[i][j]+=f[i-1][k];
            }
        }
    }
    ll ans=0;
    for(int i=0;i<=mmax;i++){
        ans+=f[m][i];
        ans%=100000000;
    }
    printf("%lld\n",ans);
    return 0;
}

题型二、排列中的状压DP

2.1 P2915 [USACO08NOV]Mixed Up Cows G

题目描述

Each of Farmer John's N (4 <= N <= 16) cows has a unique serial number S_i (1 <= S_i <= 25,000). The cows are so proud of it that each one now wears her number in a gangsta manner engraved in large letters on a gold plate hung around her ample bovine neck.

Gangsta cows are rebellious and line up to be milked in an order called 'Mixed Up'. A cow order is 'Mixed Up' if the sequence of serial numbers formed by their milking line is such that the serial numbers of every pair of consecutive cows in line differs by more than K (1 <= K <= 3400). For example, if N = 6 and K = 1 then 1, 3, 5, 2, 6, 4 is a 'Mixed Up' lineup but 1, 3, 6, 5, 2, 4 is not (since the consecutive numbers 5 and 6 differ by 1).

How many different ways can N cows be Mixed Up?

For your first 10 submissions, you will be provided with the results of running your program on a part of the actual test data.

POINTS: 200

约翰家有N头奶牛,第i头奶牛的编号是Si,每头奶牛的编号都是唯一的。这些奶牛最近 在闹脾气,为表达不满的情绪,她们在挤奶的时候一定要排成混乱的队伍。在一只混乱的队 伍中,相邻奶牛的编号之差均超过K。比如当K = 1时,1, 3, 5, 2, 6, 4就是一支混乱的队伍, 而1, 3, 6, 5, 2, 4不是,因为6和5只差1。请数一数,有多少种队形是混乱的呢?

输入格式

  • Line 1: Two space-separated integers: N and K

  • Lines 2..N+1: Line i+1 contains a single integer that is the serial number of cow i: S_i

输出格式

  • Line 1: A single integer that is the number of ways that N cows can be 'Mixed Up'. The answer is guaranteed to fit in a 64 bit integer.

输入输出样例

输入 #1

4 1
3
4
2
1

输出 #1

2

说明/提示

The 2 possible Mixed Up arrangements are:

3 1 4 2

2 4 1 3

分析

\(n\)的值达到了\(16\),如果我们去枚举全排列的话,必定会超时
所以这就要用到一种新的定义方式\(f[i][j]\)为状态为\(i\)的人已经排好了队,队尾的人为\(j\)时合法的方案数
在进行状态转移之前,我们要把队伍中人数为\(1\)的情况预处理一下

    for(iint i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        f[1<<(i-1)][i]=1;
    }

接下来就是状态转移方程

    for(iint i=0;i<=mmax;i++){
        for(iint j=1;j<=n;j++){
            if((i&(1<<(j-1)))==0) continue;
            for(iint k=1;k<=n;k++){
                if(k!=j && i&(1<<(k-1)) && abs(a[k]-a[j])>kk){
                    f[i][j]+=f[i^(1<<(j-1))][k];
                } 
            }
        }
    }

第一重循环枚举当前状态
第二重循环枚举当前状态下在队尾的人
第三重循环枚举上一状态在队尾的人
如果当前状态在队尾的人和上一状态在队尾的人满足条件,就可以转移,否则就不能转移

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=17;
typedef long long iint;
iint f[1<<maxn][maxn],a[maxn];
int main(){
    iint n,kk;
    scanf("%lld%lld",&n,&kk);
    for(iint i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        f[1<<(i-1)][i]=1;
    }
    iint mmax=(1<<n)-1;
    for(iint i=0;i<=mmax;i++){
        for(iint j=1;j<=n;j++){
            if((i&(1<<(j-1)))==0) continue;
            for(iint k=1;k<=n;k++){
                if(k!=j && i&(1<<(k-1)) && abs(a[k]-a[j])>kk){
                    f[i][j]+=f[i^(1<<(j-1))][k];
                } 
            }
        }
    }
    iint ans=0;
    for(iint i=1;i<=n;i++) ans+=f[mmax][i];
    printf("%lld\n",ans);
    return 0;
}

2.2 SSY的队列

题目描述

SSY是班集体育委员,总喜欢把班级同学排成各种奇怪的队形,现在班级里有N个身高互不相同的同学,请你求出这N个人的所有排列中任意两个相邻同学的身高差均不为给定整数M的倍数的排列总数。

输入格式

共三行:
第一行为N
第二行为N个不同的整数
第三行为M

输出格式

一行,为符合条件的排列总数(答案对1234567891取余数)。

样例

样例输入1

3
-1 0 1
2

样例输出1

2

样例输入2

4
1 2 3 4
3

样例输出2

12

数据范围与提示

20%的数据:N<=11

70%的数据:N<=15

100%的数据:N<=30,M<=1000。

分析

这一道题状压不是正解,但是却可以拿到相当可观的分数,代码和上一题基本相同

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=23;
const ll mod=1234567891;
ll f[1<<maxn][maxn],a[maxn];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        f[1<<(i-1)][i]=1;
    }
    ll m;
    scanf("%lld",&m);
    int mmax=(1<<n)-1;
    for(int i=0;i<=mmax;i++){
        for(int j=1;j<=n;j++){
            for(int k=1;k<=n;k++){
                if(abs(a[j]-a[k])%m==0 || (i&(1<<(j-1))==0) || (i&(1<<(k-1))==0)) continue;
                f[i][j]+=f[i^(1<<(j-1))][k];
                f[i][j]%=mod;
            }
        }
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        ans+=f[mmax][i];
        ans%=mod;
    }
    printf("%lld\n",ans);
    return 0;
}

题型三、状压DP求最小分组问题

3.1 P3052 [USACO12MAR]Cows in a Skyscraper G

题目描述

A little known fact about Bessie and friends is that they love stair climbing races. A better known fact is that cows really don't like going down stairs. So after the cows finish racing to the top of their favorite skyscraper, they had a problem. Refusing to climb back down using the stairs, the cows are forced to use the elevator in order to get back to the ground floor.

The elevator has a maximum weight capacity of W (1 <= W <= 100,000,000) pounds and cow i weighs C_i (1 <= C_i <= W) pounds. Please help Bessie figure out how to get all the N (1 <= N <= 18) of the cows to the ground floor using the least number of elevator rides. The sum of the weights of the cows on each elevator ride must be no larger than W.

给出n个物品,体积为w[i],现把其分成若干组,要求每组总体积<=W,问最小分组。(n<=18)

输入格式

  • Line 1: N and W separated by a space.

  • Lines 2..1+N: Line i+1 contains the integer C_i, giving the weight of one of the cows.

输出格式

  • A single integer, R, indicating the minimum number of elevator rides needed.

one of the R trips down the elevator.

输入输出样例

输入 #1

4 10
5
6
3
7

输出 #1

3

分析

我们设\(f[i]\)为当前状态为\(i\)时最小的乘电梯次数
最终的答案就是\(f[(1<<n)-1]\)
我们同时要开一个数组g来记录当前状态下电梯内剩余的体积
注意初始的时候要把\(f[0]\)置为\(1\),\(g[0]\)置为\(w\)

说明/提示

There are four cows weighing 5, 6, 3, and 7 pounds. The elevator has a maximum weight capacity of 10 pounds.

We can put the cow weighing 3 on the same elevator as any other cow but the other three cows are too heavy to be combined. For the solution above, elevator ride 1 involves cow #1 and #3, elevator ride 2 involves cow #2, and elevator ride 3 involves cow #4. Several other solutions are possible for this input.

代码

#include<bits/stdc++.h>
using namespace std;
int n,w;
int a[20];
int f[1<<18];
int g[1<<18];
int main(){
	scanf("%d%d",&n,&w);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	memset(f,0x3f,sizeof(f));
	f[0]=1;
	g[0]=w;	
	for(int i=0;i<(1<<n);i++){
		for(int j=1;j<=n;j++){
			if(i&(1<<(j-1))) continue;
			if(g[i]>=a[j]&&f[i|(1<<(j-1))]>=f[i]){
				f[i|(1<<(j-1))]=f[i];
				g[i|(1<<(j-1))]=max(g[i|(1<<(j-1))],g[i]-a[j]);
			}
			else if(g[i]<a[j]&&f[i|(1<<(j-1))]>=f[i]+1){
				f[i|(1<<(j-1))]=f[i]+1;
				g[i|(1<<(j-1))]=max(g[i|(1<<(j-1))],w-a[j]);
			}
		}
	}
	printf("%d",f[(1<<n)-1]);
	return 0;
}

3.2 小猫爬山

题目描述


分析

和上一道完全一样

代码



#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int lqs=3e5+10;
ll Mx,val[lqs],cnt,n,w,f[1<<20];
bool in[lqs];
void dfs(ll tot,ll now,ll s){
	if(now>n||tot>w||in[s])return;
	if(!in[s]){
		in[s]=1;
		for(register ll i=0;i<Mx;i++)
			f[i|s]=min(f[i|s],f[i]+1);
	}
	for(ll i=now;i<=n;i++){
		dfs(tot,now+1,s);
		dfs(tot+val[i],now+1,s|1<<i-1);
	}
}
int main(){
	scanf("%lld%lld",&n,&w);
	for(ll i=1;i<=n;i++)
		scanf("%lld",&val[i]);
	memset(f,0x3f,sizeof(f));
	f[0]=0;
	Mx=1<<n;	
	dfs(0,1,0);
	printf("%lld\n",f[Mx-1]);
}
posted @ 2020-07-03 18:08  liuchanglc  阅读(682)  评论(3编辑  收藏  举报