模拟赛20210809A T1
题目
大意:一个长度为
n
n
n的序列,求经过
n
−
1
n-1
n−1次合并操作后所有可能的结果(从小到大输出)
定义合并操作为:每次操作可将任意一对相邻的数
a
,
b
a,b
a,b 合并成
(
(
a
&
b
)
+
(
a
∣
b
)
>
>
1
((a\&b)+(a | b)>>1
((a&b)+(a∣b)>>1。
思路
区间DP:
设
f
i
,
j
,
k
f_{i,j,k}
fi,j,k 表示
i
i
i 到
j
j
j 这个区间内能否经过若干次合并得到
k
k
k,能为1,不能为0。
因为
(
(
a
&
b
)
+
(
a
∣
b
)
>
>
1
((a\&b)+(a | b)>>1
((a&b)+(a∣b)>>1 实际上等于
(
a
+
b
)
/
2
(a+b)/2
(a+b)/2,而
a
i
a_i
ai最大为7,所以所有结果都不会超过7。
第一层循环枚举区间长度
第二层枚举区间位置(一个端点)
第三层枚举中间点
m
i
d
mid
mid,表示将区间分为左区间
[
i
,
m
i
d
]
[i,mid]
[i,mid] 和右区间
[
m
i
d
+
1
,
j
]
[mid+1,j]
[mid+1,j]。
第四层枚举左区间能合并成的结果
p
p
p,即
f
[
i
]
[
m
i
d
]
[
p
]
=
=
1
f[i][mid][p]==1
f[i][mid][p]==1
第五层枚举右区间能合并成的结果
q
q
q,即
f
[
m
i
d
+
1
]
[
j
]
[
q
]
=
=
1
f[mid+1][j][q]==1
f[mid+1][j][q]==1
因为左区间能合并成
p
p
p,右区间能合并成
q
q
q,所以整个区间能合并成
(
p
+
q
)
/
2
(p+q)/2
(p+q)/2
即当
f
[
i
]
[
m
i
d
]
[
p
]
=
=
1
f[i][mid][p]==1
f[i][mid][p]==1 且
f
[
m
i
d
+
1
]
[
j
]
[
q
]
=
=
1
f[mid+1][j][q]==1
f[mid+1][j][q]==1 时 ,
f
[
i
]
[
j
]
[
(
p
+
q
)
/
2
]
=
1
\ \ f[i][j][(p+q)/2]=1
f[i][j][(p+q)/2]=1
最后判断
f
1
,
n
,
i
f_{1,n,i}
f1,n,i中可以合并的数。
因为
1
<
=
i
<
=
m
i
d
<
j
<
=
n
1<=i<=mid<j<=n
1<=i<=mid<j<=n,
0
<
=
k
<
=
7
\ \ \ 0<=k<=7
0<=k<=7
所以时间复杂度为跑不满的
O
(
n
3
8
2
)
=
O
(
64
n
3
)
,
对
于
此
题
n
<
=
150
是
可
以
过
的
O(n^38^2)=O(64n^3),对于此题n<=150是可以过的
O(n382)=O(64n3),对于此题n<=150是可以过的
代码
#include<iostream>
#include<cstdio>
#define reg register
using namespace std;
int n,a[160];
bool f[160][160][10];
int mer(int a,int b)//合并操作
{
return (((a&b)+(a|b))>>1);
}
int main()
{
scanf("%d",&n);
for(reg int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
f[i][i][a[i]]=true;//初始化
}
for(reg int i=2;i<=n;++i)//枚举区间长度
{
for(reg int j=i;j>=1;--j)//枚举区间左端点(要倒着)
{
for(reg int mid=j;mid<i;++mid)//枚举中间点
{
for(reg int l=0;l<=7;++l)//枚举左区间能不能合并的数
{
if(!f[j][mid][l]) continue;//不能就跳过
for(reg int r=0;r<=7;++r)//枚举右区间能不能合并的数
{
if(!f[mid+1][i][r]) continue;//不能就跳过
int t=mer(l,r);//合并操作
f[j][i][t]=true;
}
}
}
}
}
for(reg int i=0;i<=7;++i)
{
if(f[1][n][i]) printf("%d ",i);//判断并输出
}
}