大致题意: 让你把一个序列分成若干段((Ale)段数(le B)),使得每一段元素之和按位取或的结果最小。
前言
完了完了,感觉自己的思维已经僵化了,看到这种题第一反应二分+贪心,然后就掉坑里出不来了。
而且我似乎就陷在贪心里了,之后无论推到哪一步,第一反应就是如何贪心。。。
更有甚者,我连题目都没读完整,居然没看见这是一道二合一。。。
按位枚举+(DP)
首先,这种二进制运算的题目我们应该从高到低枚举每一位,使得较高位尽量小。
则我们需要考虑如何验证,这就可以想到一个(DP):
设(f_{i,j})表示(DP)到第(i)位,共分为(j)段是否可行。则我们枚举之前的一个位置(k),若(sum_{x=k+1}^ia_x)中当前这一位以及之前已确定为答案的位不含(1),就可以从(f_{k,j-1})转移得来。((sum a_x)可以前缀和预处理)
虽然这样显然过不了全部数据,但最后一个子任务(A=1)。。。
因此,我们只要设(f_i)表示(DP)到第(i)位,至少需要分成多少段,然后按同样的方式转移即可。
具体实现详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
#define LL long long
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
int n,Mn,Mx,a[N+5];LL s[N+5];
namespace SmallSituation//针对小数据
{
int f[N+5][N+5];
I bool Check(Con LL& Ban)//Ban表示禁止位
{
RI i,j,k;for(f[0][0]=i=1;i<=n;++i) for(j=1;j<=Mx;++j)
for(f[i][j]=k=0;k^i;++k) if(!((s[i]-s[k])&Ban)&&f[k][j-1]) {f[i][j]=1;break;}
for(i=Mn;i<=Mx;++i) if(f[n][i]) return 1;return 0;
}
}
namespace NoLowerBound//针对无下界的数据
{
int f[N+5];
I bool Check(Con LL& Ban)//和上面的转移方式几乎一样
{
RI i,k;for(i=1;i<=n;++i)
for(f[i]=Mx+1,k=0;k^i;++k) !((s[i]-s[k])&Ban)&&Gmin(f[i],f[k]+1);//不含禁止位才能转移
return f[n]<=Mx;
}
}
int main()
{
RI i;for(scanf("%d%d%d",&n,&Mn,&Mx),i=1;i<=n;++i) scanf("%d",a+i),s[i]=s[i-1]+a[i];//统计前缀和
LL ans=0,Ban=0;for(i=42;~i;--i) Ban|=1LL<<i,//从高到低枚举每一位,先禁止掉当前位
!(Mn^1?SmallSituation::Check(Ban):NoLowerBound::Check(Ban))&&(ans|=1LL<<i,Ban^=1LL<<i);
return printf("%lld",ans),0;//输出答案
}