题目范围如果要求用 l o n g l o n g long\ long long long,在左移的时候一定要写成 1 L L 1LL 1LL!
题意
巴厘岛的一条主干道上共有
N
N
N座雕塑,依次编号为
1
1
1到
N
N
N。雕塑
i
i
i的年龄为
Y
i
Y_i
Yi。
政府想把这些雕塑分成恰好
X
X
X组,要求每组不能为空,且每组雕塑的编号必须连续。每个雕塑必须属于某一组。
分组方案需要考虑美观程度。计算方法如下:分别计算每组雕塑的年龄之和,然后将每一组的结果按位取或,就得到了该分组方案的美观值。求最小的美观值
S o l u t i o n Solution Solution
这道题首先想到题目中 A 、 B A、B A、B限制分组条件,又看到数据范围分为 A = = 1 A==1 A==1和 A ! = 1 A!=1 A!=1两种情况,于是显然限制条件不同需要分情况讨论。
A ! = 1 A!=1 A!=1
这道题看起来是限制条件下的贪心,考虑如何贪心,显然最后答案的和一定是由
01
01
01串组成的,于是考虑如何分组使得
01
01
01串最小,显然这种思路可以转化为:构造一个
a
n
s
ans
ans看
a
n
s
ans
ans是否符合
A
B
A\ B
A B分组的限制条件。则考虑先构造一个一定最不优的
a
n
s
ans
ans并从高位到低位逐位将
a
n
s
ans
ans上的
0
0
0转换成
1
1
1看是否可行,验证构造的
a
n
s
ans
ans即可。
先令
a
n
s
ans
ans为当前最不优的情况(使其完全满足可贪心递推的条件),枚举每个
a
n
s
ans
ans看它是否符合贪心思想。
b
o
o
l
bool
bool型数组
f
i
,
k
f_{i,k}
fi,k表示前
i
i
i个数,分成
k
k
k组这种情况是否可行。根据
i
i
i值从前往后递推,如果此时讨论的连续序列符合当前的贪心思想,则对此时情况标记为可行。判断每个
a
n
s
ans
ans并更新,则一定能得到最优解。
代码细节问题:
d
p
dp
dp记得赋初值,要从已经赋过值的状态转移(虽然也许这种状态下的情况和实际矛盾,比如
0
0
0个数分
0
0
0组的方案数为
1
1
1)。构造前缀和来直接检验那一段数列是否符合贪心,
(
(
s
[
k
]
−
s
[
i
]
)
∣
x
)
=
=
x
((s[k]-s[i])|x)==x
((s[k]−s[i])∣x)==x,因为是“或”不是“异或”,所以这样就表示当前这样分组后符合贪心条件,不会使贪心变得不优。
曾经的疑惑:其实
i
i
i到
k
k
k这段序列也不一定单独分一组更优,但是我们必须使分组条件满足组数
≥
A
\geq A
≥A,而在不会使答案更劣的情况下这样必定会使答案更符合题目限制。
A = = 1 A==1 A==1
此时大体思路和上面差不多,但是注意到这个时候
N
N
N很大,又因为我们没有了最低分组数目的限制,所以可以直接将
d
p
dp
dp圧掉一维,用
f
i
f_i
fi表示前面
i
i
i个数分成多少组,检验最后分组数
f
n
f_n
fn是否满足
≤
B
\leq B
≤B即可。
代码细节问题:这里
f
i
f_i
fi因为是取
m
i
n
min
min所以要记得赋最大值,同上赋初值
f
0
=
0
f_0=0
f0=0
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2005;
int s[N],ans,n,a,b;
inline int read(){
int cnt=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
while(isdigit(c)){cnt=(cnt<<1)+(cnt<<3)+(c^48);c=getchar();}
return cnt*f;
}
bool f[105][105];
int f2[2005];
inline bool check(int x){
memset(f,0,sizeof(f));
f[0][0]=1;
for(int i=0;i<n;++i){//要从0开始 因为是从0赋的值 要不然推不过去
for(int j=0;j<b;++j){
for(int k=i+1;k<=n;k++){
if(((s[k]-s[i])|x)==x){
f[k][j+1]|=f[i][j];
//也不一定 i+1 要单独成一位 但是要满足a这个下限 所以在符合条件的时候要另开一组(k+1)保证更优
}
}
}
}
for(int i=a;i<=b;i++)if(f[n][i]) return true;
return false;
}
inline bool check2(int x){
memset(f2,63,sizeof(f2));f2[0]=0;//一定要记得赋最大值
for(int i=0;i<n;i++){
for(int k=i+1;k<=n;k++){
if(((s[k]-s[i])|x)==x)
f2[k]=min(f2[k],f2[i]+1);//因为a==1 所以不考虑下限 圧掉一维 直接判在最后是否符合b
}
}
if(f2[n]<=b) return true;
return false;
}
signed main(){
n=read(),a=read(),b=read();
for(int i=1;i<=n;++i){s[i]=read();s[i]+=s[i-1];}
if(a!=1){
ans=(1LL<<50)-1;//相当于ans现在在二进制下是由49个1构成
for(int i=49;i>=0;i--){
ans-=(1LL<<i);//将第i个1变成0 并判断此时贪心是否合法
if(!check(ans))ans+=(1LL<<i);//如果此时的ans不满足条件 则还原ans
}
cout<<ans<<endl;
}
if(a==1){
ans=(1LL<<50)-1;
for(int i=49;i>=0;i--){
ans-=(1LL<<i);
if(!check2(ans))ans+=(1LL<<i);
}
cout<<ans<<endl;
}
return 0;
}