状态压缩与二进制

自己最烦二进制,但还是痛定思痛,耐心学着...

普通的数都是十进制,二进制便是逢2进一,我觉得它最大的用途就可以表示一系列的东西用于不用上,用则为1,不用为0,比如说四个物体,可以用1111表示,再把1111用十进制表示15,这样15就可以表示这一状态。

说从最基础的说起:一般的n个物体,用二进制表示就需要有n个位置,这时的十进制是多少呢?可以用<<表示,例如1<<2,就表示100,那n个物体就需要1<<n,才能表示1后n个0,前面要多一个1,因为你要把n个1的情况也包括,例如:n=4,1<<4就表示10000,开数组后从0计数,就可以包括1111这种状态了,其实不难发现,1<<n和2的n次方是一样的.所以数据范围一般就是2的n次方倍时就可以考虑状压了。

其次:要明白一些最基本的位运算的用法:&表示与,有0取0,|表示或,有1取1,^表示异或,相同取0,否则取1.这个对我来说挺难记得,我是这样记得:&,|,^,这三个顺序记好,之后就是0,1,是否相等...还有就是<<=就是数要向左移几位,后面用0补齐,例如:4<<2,4用二进制表示为100,操作后变成10000;相反>>=就是原数想右移,最后几位舍去,例如:4>>2就从100变成了1;

说一些其他用法,基本上是状压DP需要的:如何判断一个二进制数的某一位是否为1或0呢?可以用&,|与1的左移,例如:if(i&1<<(j-1)) continue;表明如果i的二进制数如果第j位为1的话,就跳过;其实很好理解,1<<(j-1)就表示一个第j位为1,其余位为0的二进制数。此事用i与它做&运算,有0取0,所以除了第j位,其余位一定取0,那么如果第j位为0,整体就是0,不执行.相反,如果第j位为1,整体有值,进行continue。如果想要判断为0是跳过,只需在前面加上!即可.之后再说如何修改?即例如1011,如何改成1111,把0改成另一个状态.这时我们就要用上|,也就是有1取1,例如:i|1<<(j-1)(此时已判断过i的第j位为0)就表示i的第j位由0变成1.这个很好理解由于1<<(j-1)除了第j位上为1,其余位上都是0,此时的|相当于把i的其他位复制下来,只把第j位修改为1;

好了说了这么多见题:

此题我第一次看就想不到DP,因为它是排列型的问题.不知道该怎么用状态表示,之后才明白这时状压DP的例题...

既然是排列,那我们就只能标记用于不用,用用过的推没用过的...

代码:

#include<bits/stdc++.h>
#define ll long long
#define _ 0
using namespace std;
const int maxn=20;
ll f[1<<maxn][maxn],n,H,h[maxn],ans;
int main()
{
    freopen("1.in","r",stdin);
    cin>>n>>H;
    for(int i=1;i<=n;i++) cin>>h[i];
    for(int i=1;i<=n;i++) f[1<<(i-1)][i]=1; //对第i位只放i的初始化;
    for(int i=1;i<=(1<<n)-1;i++)
        for(int j=1;j<=n;j++) //枚举上一个 
            for(int k=1;k<=n;k++) //枚举当前放的
            {
                if(i&1<<(k-1)) continue; //假如说当前k已经放过则跳过.
                if(abs(h[k]-h[j])>H) f[i|1<<(k-1)][k]+=f[i][j];
            }
    for(int i=1;i<=n;i++) ans+=f[(1<<n)-1][i];
    cout<<ans<<endl;
    return (0^_^0);         
}

好了,就到这吧!

原文地址:https://www.cnblogs.com/gcfer/p/11076578.html