100道动态规划——24 UVA 1633 Dyslexic Gollum 状态压缩DP 挺好的题 因为窝没想到嘛

    没做出来,看了http://blog.csdn.net/chy20142109/article/details/52266353 这里的做法之后模拟的一遍,当初就没有想到是状态压缩=_=,不过现在想一想觉得状态压缩是很有道理的,总之还是涨了姿势吧。

    题目的意思很清楚,给定n和k,求出长度为n且最长回文子串的长度<k的01串的数量。注意是最长回文子串,不是最长回文子序列,当时脑子一热,想成了最长回文子序列,然后一想,最长回文子序列的长度最小也有n>>1......

    先说说构造回文串的问题,构造长度为i的01回文串,枚举长度为i>>1的01串即可,假若i&1的话,还分最中间为0和最中间为1的情况,依据回文的特性,另一边可以对称过去,因此需要一个反转某个数的二进制位的操作,我在我的 http://blog.csdn.net/good_night_sion_/article/details/53148718 代码收集里面有记录反转一个数的二进制位,在具体实现的时候还有一些细节,就看代码的时候再提。

    状态:定义dp[i][j]表示长度为i的串的最后k位为j时的合法串的数量,该状态可以转到dp[i+1][f(j)+0]和dp[i+1][f(j)+1],其中f(j)表示把j的最高位置0后向左移动一位,即考虑在末尾添加一个0或1。但是这样跟着来的一个问题就是,我们要确保添加一个0或者1后状态是合法的(因为之前是合法的,确保这一次添加后合法即可),于是就利用了一个额外的bool数组pali[i][j]记录长度为i字符串为j是否含有长度>=k的最长回文子串。至于确定pali的方法,根据刚刚的构造方法可以做出来。

    继续说回转移的问题,刚刚说到要确保添加一个0或者1后状态是合法的,即最后k位是合法的。实际上还应该考虑的更多一点,同时还应该要考虑最后k+1位是不是回文串。拿那边的例子,就是k=4时,由0010转移到00100的时候,最后4位不是回文串,但是最后5位却是回文串。为了防止这种情况的出现,我们应该同时考虑后k+1位。

    转移说完了,pali的构造也说完了,具体参见代码

#include <iostream>
#include <cstring>

using namespace std;
using ll = long long;

inline unsigned rever(unsigned j,unsigned k);
inline int get_newstate(int j,int x);

constexpr int bit[]{0,1,1<<1,1<<2,1<<3,1<<4,1<<5,1<<6,1<<7,1<<8,1<<9,1<<10,1<<11,1<<12},modulu=1000000007;
int n,k,t;
bool pali[12][(1<<11)+10];
void init();
ll dp[410][(1<<11)+10],slove();

int main(){
    ios_base::sync_with_stdio(false);
    init();
    cin>>t;
    while(t--){
        cin>>n>>k;
        cout<<slove()<<endl;
    }
    return 0;
}

void init(){
    pali[1][0]=pali[1][1]=true;//在进行右移的时候,假若被移动的数是负数,此时左边填充的是1而不是0,int在反转之后很可能变成负数,因此用unsigned
    for(unsigned i=2,half=i>>1,limi=(1<<half)-1;i<=11;limi=(1<<(half=++i>>1))-1)
    for(unsigned j=0,r=rever(j,half);j<=limi;r=rever(++j,half))//这里就是反转构造回文串
    if(i&1)
        pali[i][(j<<1)<<half|r]=pali[i][(j<<1|1)<<half|r]=true;
    else
        pali[i][j<<half|r]=true;
}

ll slove(){
    ll ans=0;
    int limi=(1<<k)-1;

    dp[0][0]=1;
    for(int i=1;i<=n;++i)
    for(int j=0;j<=limi;++j)
    if(dp[i-1][j])
    for(int x=0,newstate=get_newstate(j,x);x<=1;newstate=get_newstate(j,++x))
    if(!((i>=k&&pali[k][newstate])||(i>=k+1&&pali[k+1][j<<1|x])))//这里就是保证后k位和后k+1为合法
        dp[i][newstate]=(dp[i][newstate]+dp[i-1][j])%modulu;

    for(int i=0;i<=limi;++i)
        ans=(ans+dp[n][i])%modulu;


    memset(dp,0,sizeof dp);
    return ans;
}

unsigned rever(unsigned x,unsigned k){
    x = ((x >> 1) & 0x55555555) | ((x << 1) & 0xaaaaaaaa);
    x = ((x >> 2) & 0x33333333) | ((x << 2) & 0xcccccccc);
    x = ((x >> 4) & 0x0f0f0f0f) | ((x << 4) & 0xf0f0f0f0);
    x = ((x >> 8) & 0x00ff00ff) | ((x << 8) & 0xff00ff00);
    x = ((x >>16) & 0x0000ffff) | ((x <<16) & 0xffff0000);
    return x>>(32-k);
}

int get_newstate(int j,int x){//最高位置0,然后左移一位,然后+x
    if(j>=bit[k])
        j-=bit[k];
    return j<<1|x;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值