Pieces 状态压缩DP

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sdz20172133/article/details/82934180

Pieces

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 1532    Accepted Submission(s): 800

 

Problem Description

You heart broke into pieces.My string broke into pieces.But you will recover one day,and my string will never go back.
Given a string s.We can erase a subsequence of it if this subsequence is palindrome in one step. We should take as few steps as possible to erase the whole sequence.How many steps do we need?
For example, we can erase abcba from axbyczbea and get xyze in one step.

 

 

Input

The first line contains integer T,denote the number of the test cases. Then T lines follows,each line contains the string s (1<= length of s <= 16).
T<=10.

 

 

Output

For each test cases,print the answer in a line.

 

 

Sample Input

2 aa abb

 

 

Sample Output

1 2

 

 

Source

2013 Multi-University Training Contest 3

 

 

Recommend

 

算法分析:

题意:

给出一个字符串,每次可以删除一个回文子序列,问最少删除多少次才能给这个字符串全部删完

分析:

对一个长度为len 的字符串,用0和1来表示某个字符在子串中是否存在,则总共有 1<<len种情况,我们进行预处理,即针对每种情况算出它是否是回文的,然后用一个标记数组ok[i]来存下来,之后进行DP .

代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
char s[20];
int dp[(1<<16)+5],sign[(1<<16)+5];
int judge(int x){ //判断是否为回文
    int l,r,len;
    len=strlen(s);
    l=0,r=len-1;
    while(l<r){
        while(!(x&(1<<l)))  //找到子状态最左边的1
        l++;
        while(!(x&(1<<r)))  //找到子状态最右边的1
        r--;
        if(s[l]!=s[r])
        return 0;
        l++,r--;
    }
    return 1;
}                                              
int main(){                                     //dp[s]表示状态是s时最少删除的次数
    int t,i,j,len;                              //dp[s]=min(dp[s],dp[s-tmp]+1),tmp为s
    scanf("%d",&t);                             //的一个子状态并且是回文子序列
    while(t--){
        scanf("%s",s);
        len=strlen(s);
        memset(dp,INF,sizeof(dp));
        memset(sign,0,sizeof(sign));
        dp[0]=0;
        
        for(i=1;i<(1<<len);i++)
         if(judge(i))                            //预处理出哪些状态是回文
             sign[i]=1;
        
        for(i=0;i<(1<<len);i++){
            for(j=i;j>0;j=i&(j-1)){             //枚举每个i的子状态(任意删1)
                if(sign[j])
                dp[i]=min(dp[i],dp[i-j]+1);
            }
        }
        printf("%d\n",dp[(1<<len)-1]);
    }
    return 0;
}

第二种做法:

对于每种字串状态i,枚举包含状态i的状态j(既i中有1的位置j也有),然后判断状态j表示的字串消除的串i^j是否是回文串,是得话就可以从状态j到达状态i

对于如何枚举包含状态i的状态j:

for(int j=i;j<2^len;j=(j+1)|i);

比如:

i:1 1 0 1 0

j;1 1 0 1 0

则j+1:1 1 0 1然后(j+1)|i就将i中第一个为0的位置变为1

然后继续(j+1)|i其实就是在原前面已变位置的前提下,如果该位置前面还有0的就变成1,否则找下一个位置变为1

DP方程为     dp[i]=min(dp[i],dp[j]+1)   //j为包含i的某个状态

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int MAXN=(1<<16)+10;
const int INF=1<<30;
char s[20];
bool ok[MAXN];
int dp[MAXN];
int len,bit;
bool check(int x)
{
    if(x==0) return true;
    int i=0,j=len-1;
    while(i<j)
    {
         while(!(x&(1<<i))) i++;
         while(!(x&(1<<j))) j--;
         if(s[i]!=s[j])
            return false;
         i++;
         j--;
    }
    return true;
}
int main()
{
    int T;
    while(cin>>T)
    {
        while(T--)
        {
            cin>>s;
            len=strlen(s);
            bit=1<<len;
            dp[bit-1]=0;
           // cout<<len<<" "<<bit<<endl;
            memset(ok,false,sizeof(ok));
            for(int i=0;i< bit;i++)
            {
                ok[i]=check(i);//改成check[i]居然也能编译通过。。。。真怀疑我是不是眼睛花了。。。
            }
            for(int i= bit-2;i>=0;i--)
            {
                dp[i]=INF;
                for(int j=i;j<bit;j=(j+1)|i)
                {
                    if(!ok[i^j]) continue;
                    dp[i]=min(dp[i],dp[j]+1);
                }
            }
            cout<<dp[0]<<endl;
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/sdz20172133/article/details/82934180
今日推荐