[BZOJ4069]-[Apio2015]巴厘岛的雕塑-位运算的贪心dp

说在前面

并没有什么想说的,但是要保持格式=w=


题目

BZOJ4069传送门 这是个权限题
洛谷P3646传送门

题面就不概括了…subtask略多可以去看看


解法

首先这个题询问的是最小值,于是me一开始想到二分答案,然而并不具有连续性。如果用小于等于答案去二分,check又很麻烦
于是这个时候就要坚定一种信念!位运算是相互独立的,因此应该可以从高到低贪心的确定答案

对于那些 N100 N ≤ 100 的数据,明显是可以接受 N3log N 3 l o g 的算法。于是我们可以这么定义:dp[i][j]表示前i个分了j组,并且每一个分组的高位与已经确定的ans相符,且当前待确定位为0,是否有一种合法方案。转移很简单。最后只需要看一看 dp[N][A...B] d p [ N ] [ A . . . B ] 中是否有一个为true就可以了

然而最后一个点 N2000 N ≤ 2000 ,并不能这样做。不过它有一个特殊性质,就是 A=1 A = 1 ,也就是说只要不超出分组上限就好了。因为没有下限,所以这个东西具有最优决策。定义dp[i]表示,前i个(条件和上面一样),最少分多少组。如果 dp[N]B d p [ N ] ≤ B 说明当前选0可行


下面是自带大常数的代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N , A , B ;
long long a[2005] , sum[2005] , ans ;

int g[2005] ;
bool checkSpe( int x ){
    memset( g , 0x3f , sizeof( g ) ) ; g[0] = 0 ;
    long long tmp , ww = 1LL << ( x - 1 ) ;
    for( int i = 1 ; i <= N ; i ++ ){
        for( int j = 0 ; j < i ; j ++ ){
            tmp = sum[i] - sum[j] ;
            if( ( ( tmp >> x ) | ( ans >> x ) ) != ( ans >> x ) ) continue ;
            if( !( tmp & ww ) && g[i] > g[j] )
                g[i] = g[j] + 1 ;
        }
    } return g[N] <= B ;
}

bool dp[105][105] ;
bool check( int x ){
    memset( dp , 0 , sizeof( dp ) ) ;
    dp[0][0] = true ;
    long long tmp , ww = 1LL << ( x - 1 ) ;
    for( int k = 1 ; k <= B ; k ++ ){
        for( int i = 1 ; i <= N ; i ++ ){
            for( int j = 0 ; j < i ; j ++ ){
                tmp = sum[i] - sum[j] ;
                if( ( ( tmp >> x ) | ( ans >> x ) ) != ( ans >> x ) ) continue ;
                if( !( tmp & ww ) && dp[k-1][j] ){
                    dp[k][i] = true ; break ;
                }
            }
        }
        if( k >= A && dp[k][N] ) return true ;
    } return false ;
}

void solve(){
    for( int i = 40 ; i >= 0 ; i -- )
        if( A == 1 ) ans += checkSpe( i + 1 ) ? 0 : ( 1LL << i ) ;
        else ans += check( i + 1 ) ? 0 : ( 1LL << i ) ;
    printf( "%lld" , ans ) ;
}

int main(){
    scanf( "%d%d%d" , &N , &A , &B ) ;
    for( int i = 1 ; i <= N ; i ++ )
        scanf( "%lld" , &a[i] ) , sum[i] = a[i] ;
    for( int i = 1 ; i <= N ; i ++ ) sum[i] += sum[i-1] ;
    solve() ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值