1. 程式人生 > >Pieces 狀態壓縮DP

Pieces 狀態壓縮DP

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

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;
}