题目大意:有一个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;
}