• 周二. 11 月 12th, 2024

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

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

admin

11 月 28, 2021

题目链接

题目大意

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

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

题目思路

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

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

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

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

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

则总和为(Largesum_{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
",dp[(1<<n)-1]);
    }
    return 0;
}

卷也卷不过,躺又躺不平

发表回复