bzoj4069 [Apio2015]巴厘岛的雕塑(贪心+dp+位运算)

304 篇文章 2 订阅
132 篇文章 0 订阅

把n个数分成k部分,使得每部分的和的按位或最小。我们考虑答案的最大可能值,我们贪心的从高到低枚举二进制下的每一位,为0最好,而为0的条件是要求分成的所有和的这一位上都是0,我们可以用dp去解决这个问题。(同类的问题还有求按位与最大,思路基本相同,贪心的判断此位能否为1,位运算求极值大概总会用这种套路思考吧).
dp的具体细节,对于一般的数据,a<=k<=b,我们用f[i][j]表示前i个数,分成j部分能否满足要求(要求指这一位是否为0,为了满足我们贪心的性质,前面是0的位置现在也一定要满足是0,我们用tmp记录这个信息),则f[i][j]=1需要f[k][j-1]&&s[k+1…i]满足tmp |j-1<=k< i。总复杂度O(n^3log w)。
但是这样的复杂度是过不了n=2000的数据的,我们发现对于N=2000的数据,给了特殊条件A=1.这意味着什么呢?这意味着k的定义变为分成不超过b个部分即可。我们用dp[i]表示让前i个数满足条件需要至少分成几部分,然后只需用dp[n]和b比大小即可判断这一位能否为0.复杂度是O(n^2log w)de1,可以过。
tips:1<<30会超ll,也要写成1LL<<30,注意全是0的情况,如果用log2[w]函数会返回极小值,导致错误。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define pa pair<int,int>
#define N 2010
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
int n,a,b,tot,dp[N];
bool f[N][N];
ll sum[N],ans=inf,tmp;
void dp1(){//f[i][j]表示前i个数,分成j部分,能否都满足条件 
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;++i) if(!(sum[i]&tmp)) f[i][1]=1;
    for(int j=2;j<=b;++j)
        for(int i=j;i<=n;++i){
            for(int k=j-1;k<i;++k)
                if(f[k][j-1]&&!((sum[i]-sum[k])&tmp)) f[i][j]=1;
        }
}
void dp2(){//dp[i]表示使得前i个数满足条件最少需要分成几组 
    memset(dp,inf,sizeof(dp));dp[0]=0;
    for(int i=1;i<=n;++i)
        for(int j=0;j<i;++j)
            if(!((sum[i]-sum[j])&tmp)) dp[i]=min(dp[i],dp[j]+1);
}
void solve1(){
    int m=log2(sum[n]);
    for(int i=m;i>=0;--i){//从高到低枚举每一位能否为0
        tmp+=1LL<<i;dp1();bool flag=0;//tmp二进制位上有1表明这一位现在要求必须是0 
        for(int j=a;j<=b;++j) if(f[n][j]) flag=1;
        if(!flag) tmp-=1LL<<i;
    }
    printf("%lld\n",(1LL<<m+1)-1-tmp);//把tmp每一位反过来就是答案 
}
void solve2(){
    int m=log2(sum[n]);
    for(int i=m;i>=0;--i){
        tmp+=1LL<<i;dp2();
        if(dp[n]>b) tmp-=1LL<<i;
    }
    printf("%lld\n",(1LL<<m+1)-1-tmp);
}
int main(){
//  freopen("sculpture.in","r",stdin);
    n=read();a=read();b=read();
    for(int i=1;i<=n;++i) sum[i]=sum[i-1]+read();
    if(sum[n]==0){puts("0");return 0;}
    if(n<=100) solve1();
    else solve2();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值