一道“暴力题” (meet in the middle)

题目大意:有一个n个元素的color数组,构造合法的括号序列,且使得color相同的括号,类型也相同,求方案输。如col={0 0 1 2 3 4};括号序列可以为”(())()”, “(()())”, 和 “((()))”

题解:
思路:把一个序列平分成两半,用dfs暴力出两半的状态,判断哪些状态是可以组合起来的。
如何判断:
1.第一个问题是:如何保证两段的括号序列拼起来是合法的。
只要前半段左括号比右括号的数量=后半段左括号比右括号少的数量,那么拼起来就是合法的。将搜索出来的状态按照前缀和分类存起来,结果就是对于所有前缀和s, ans=ans1[s]+ans2[s]
2.第二个问题是:如何保证两段中同一种颜色是同种括号。
预处理出哪些颜色既出现在前半段,又出现在后半段,在dfs暴力时,将这些颜色的括号种类用二进制压缩进一个整数state,只要两段state相等,这个状态就是合法的。

代码与注释:

#include<cstdio>
#include<cstring>
#include<vector>
using std::vector;
const int MAXN=55,MAXM=27,MAXS=(1<<25)+10;
int col[MAXN],id[MAXN],cnt[MAXS];
vector<int>ans1[MAXM],ans2[MAXM];
char temp[MAXN];
void dfs1(int i,int m,int state,int sum)//枚举到第i位,到第m位结束,state为那种颜色的状态,sum为前缀和'('大于')'的数量
{
    if(i>m)
    {
        ans1[sum].push_back(state);//将状态存进此前缀和的数组
        return;
    }
    if(temp[col[i]]=='\0')
    {
        temp[col[i]]='(';
        //如果当前为两边都有的颜色(id[col[i]]==-1),就要更新state,'('为1,')'为0
        dfs1(i+1,m,(~id[col[i]])?state|(1<<id[col[i]]):state,sum+1);
        if(sum>0)
        {
            temp[col[i]]=')';
            dfs1(i+1,m,state,sum-1);
        }
        temp[col[i]]='\0';
    }
    //这个颜色已经枚举过了↓
    else if(temp[col[i]]=='(')
        dfs1(i+1,m,(~id[col[i]])?state|(1<<id[col[i]]):state,sum+1);
    else
    {
        if(sum<=0)return;
        dfs1(i+1,m,state,sum-1);
    }
}
//↓相似于上面
void dfs2(int i,int m,int state,int sum)
{
    if(i<m)
    {
        ans2[sum].push_back(state);
        return;
    }
    if(temp[col[i]]=='\0')
    {
        if(sum>0)
        {
            temp[col[i]]='(';
            dfs2(i-1,m,(~id[col[i]])?state|(1<<id[col[i]]):state,sum-1);
        }
        temp[col[i]]=')';
        dfs2(i-1,m,state,sum+1);
        temp[col[i]]='\0';
    }
    else if(temp[col[i]]=='(')
    {
        if(sum<=0)return;
        dfs2(i-1,m,(~id[col[i]])?state|(1<<id[col[i]]):state,sum-1);
    }
    else
        dfs2(i-1,m,state,sum+1);
}
int main()
{
    freopen("bruteforce.in","r",stdin);
    freopen("bruteforce.out","w",stdout);
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%d",col+i);
    int m=n>>1;
    //预处理出哪些颜色是两段都有的↓(据说位运算快一点,就用了位运算大法)
    long long flag1=0,flag2=0,flag=0;
    for(int i=0;i<m;i++)
        flag1|=(1LL<<col[i]);//前半段有哪些颜色
    for(int i=m;i<n;i++)
        flag2|=(1LL<<col[i]);//后半段有哪些颜色
    flag=flag1&flag2;//两边都有的颜色
    int tot=0;
    memset(id,-1,sizeof id);
    for(int i=0;i<n;i++)//重新编号那些颜色↑
        if(flag&(1LL<<i))
            id[i]=tot++;
    //暴力两段↓
    dfs1(0,m-1,0,0);
    dfs2(n-1,m,0,0);
    //处理结果↓
    long long ans=0;
    for(int i=0;i<=m&&i<=n-m;i++)
    {
        //此时两段的前缀和为i
        //处理每种状态前半段的方案数↓
        int sz=ans1[i].size();
        for(int j=0;j<sz;j++)
            cnt[ans1[i][j]]++;
        //如果后半段状态和前面一样,加上这个方案数
        sz=ans2[i].size();
        for(int j=0;j<sz;j++)
            ans+=1LL*cnt[ans2[i][j]];
        //清零,不要用memset,卡卡卡卡卡卡卡卡。。。
        sz=ans1[i].size();
        for(int j=0;j<sz;j++)
            cnt[ans1[i][j]]--;
    }
    //输出
    printf("%lld\n",ans);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值