说在前面
并没有什么想说的,但是要保持格式=w=
题目
BZOJ4069传送门 这是个权限题
洛谷P3646传送门
题面就不概括了…subtask略多可以去看看
解法
首先这个题询问的是最小值,于是me一开始想到二分答案,然而并不具有连续性。如果用小于等于答案去二分,check又很麻烦
于是这个时候就要坚定一种信念!位运算是相互独立的,因此应该可以从高到低贪心的确定答案
对于那些 N≤100 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就可以了
然而最后一个点 N≤2000 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() ;
}