Pieces 题解(状压dp+$3^n$枚举子集)

题目链接

题目大意

有一个长度不超过 16 的字符串。每次你可以从中删除一个子序列,但是要求这个子序列是回

文的。问最少删除几次可以把这个字符串删光。

题目思路

这个数据很小 很明显是状压\(dp\)

设\(dp[i]\)表示删除\(i\)的最小操作数 那么答案显然为\(dp[(1<<n)-1]\)

然后直接枚举子集\(dp\)转移即可

所有元素子集的总个数为\(3^n\)

考虑贡献如果\(x\)集合有\(k\)个\(1\) 那么子集有\(x\)的有\(2^{n-k}\)个

则总和为\(\Large\sum_{k=0}^{k=n}C(n,k)2^{n-k}=(1+2)^n=3^n\)

枚举 x 的子集代码:
for(int i = x; i; i = (i - 1) & x){
// i 就是 x 的子集
}

代码

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define se second
#define debug cout<<"I AM HERE"<<endl;
using namespace std;
typedef long long ll;
const int maxn=1e5+5,inf=0x3f3f3f3f,mod=1e9+7;
const double eps=1e-6;
char s[maxn],temp[maxn];
bool flag[1<<16];
int dp[1<<16],n;
bool check(int x){
    int len=0;
    // [0,len-1]
    for(int i=0;i<n;i++){
        if(x&(1<<i)){
            temp[len++]=s[i];
        }
    }
    for(int i=0;i<=len-1;i++){
        if(temp[i]!=temp[len-1-i]) return 0;
    }
    return 1;
}
signed main(){
    int _;scanf("%d",&_);
    while(_--){
        scanf("%s",s);
        n=strlen(s);
        memset(dp,0x3f,sizeof(dp));
        for(int i=0;i<=(1<<n)-1;i++){
            flag[i]=check(i);
        }
        dp[0]=0;
        for(int i=1;i<=(1<<n)-1;i++){
            for(int j=i;;j=(j-1)&i){
                if(dp[j]==inf) continue;
                if(flag[i^j]){
                    dp[i]=min(dp[i],dp[j]+1);
                }
                if(!j) break;
            }
        }
        printf("%d\n",dp[(1<<n)-1]);
    }
    return 0;
}

上一篇:能否连接形成数组


下一篇:Wood Cut