说在前面
肝这题肝了几天
边比赛边肝题,终于肝出来了…开心qwq
题目
UPD:此题题源为codeforces321E
BZOJ5311传送门
题目大意
给定一个长度为
的序列,现在你需要将这个序列划分成连续的
段
被划分到同一段的两个位置
,会产生代价
,不同段不会产生代价
现在请求出最小的贡献
范围:
,
,代价矩阵
的数字均在
之间
保证:
要求:一对
的代价只计算一次
输出格式
输入格式:
第一行两个整数
,含义如题
接下来是一个
的矩阵,第
行第
列的矩阵代表
输出格式:
输出一行一个整数,表示答案
解法
这个题,me在BZOJ上T了一版,后来换了个方法终于过了
(不知道是不是me想复杂了,但me觉得这是一个很好的题
算法0
首先,我们可以写出一个很显然的DP
定义
表示,前
个分
组的最小代价
转移
如果我们把代价矩阵
的部分变成
并求前缀和,转移就可以写成这样
复杂度
算法1
考虑对算法1进行优化
我们发现,随着
的变大,靠前的转移点会慢慢变得不优。具体的,转移具有单调性如图,如果从
转移,代价为
,如果从
转移,代价为
。因为代价都是正的,所以从
转移的代价不会小于从
转移的代价,并且差值
可能越来越大
然而,由于
,因此并没有显然的单调性
但我们知道,一旦在某刻时刻,
比
优,那么它将永远比
优
所以,我们可以用一个单调队列来维护这些转移点。保证单调队列里 后一个点超越前一个点的时间单调即可。这也是 [JSOI2011]柠檬 的一种可行做法
时间复杂度
算法2
我们发现时限太小了!只有3s,而且读入数据高达 个整数,算法2仍然是不可过的,但是由于me没有想出进一步的优化,于是考虑换思路
重新审视这道题,
的数列,恰好
段,而且随着
的减小,答案会越来越大,并且
,这个
也越来越大
这启发我们想到dp凸优化(又称带权二分 / WQS 二分)
这是一类二分方法,专门用于限制「恰好
个」且「
单调」的题目中
复杂度 ,加上fread可通过此题
算法3
这是CF上原题的解法传送门
还是记
表示前
个分
组的最小代价
然后把
最优转移的最小位置记作
,即若
,则
那么在
相同的时候,显然有
根据这一性质,我们考虑分治,我们花费
先求出
和
,这样,我们就可以知道
的决策点都不会超过
(
同理),然后继续分治下去即可
伪代码大概长这样:
void solve( int d , int L , int R , int optL , int optR ){
if( L > R ) return ;
if( L == R ) special_work ;
int mid = ( L + R ) >> 1 ;
get_DP( dp[d][mid] , optL , optR ) ;//here we know opt[d][mid] as well
solve( d , L , M - 1 , optL , opt[d][mid] ) ;
solve( d , M + 1 , R , opt[d][mid] , optR ) ;
}
关于复杂度,因为同一层的决策区间加起来总长为 ,而相邻决策区间最多重合 (就是重合了 ),所以一层的复杂度上限 ,一共log层,所以单次复杂度 ,总复杂度
卧槽为什么我写完了才发现这个方法的复杂度也那么大??还是过不去BZOJ上那个神tm时限
算法4
这个方法很有意思,me还没有仔细研究,不过感觉推广性挺强的
这个题的最优决策,其实可以看作是把这个序列分的尽量「平均」。「平均」是指,每一段的代价尽量平均
我们还是定义 为 的最优转移点,那么可以发现一个性质:
我们来感性理解一下这个性质: 可以看作是,在前 个里画 根线使其尽量「平均」, 相当于是最后一根线的位置。同理, 是在前 个里画 根线, 是最后一根线的位置。因为要「平均」,所以 第一种画法最后一根线的位置,一定不会在 第二种画法最后一根线 的后面,也就是 。另一半的证明也是类似的
所以我们尝试用这个性质来缩小我们的枚举范围
第一维仍然从
到
,第二维从
到第一维(因为
中,
总得比
大)。然后第三维,我们从
枚举到
,特别的,
,
。那么这样看起来复杂度是要降低了一点
那么复杂度到底是多少呢?对于 相同的 ,复杂度为 ,相邻项抵消,最后剩下 ,显然等于 。而 的取值只有 种,所以最后复杂度是 ,可以通过此题
下面是自带大常数的代码
#include <ctime>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N , K , sum[4005][4005] ;
struct Data{
int val , ed , cnt ;
Data( int _ = 0 , int __ = 0 , int ___ = 0 ) : val(_) , ed(__) , cnt(___) {} ;
} que[4005] , dp[4005] ;
struct io
{
#define fs 48000000
char ch[fs];int nr;
io(){ch[fread(ch,1,fs,stdin)]=0;}
inline int read()
{
int x=0;
for(;ch[nr]<48;++nr);
for(;ch[nr]>47;++nr)
x=(x<<1)+(x<<3)+ch[nr]-48;
return x;
}
inline void skip()
{
for(++nr;ch[nr]>10;++nr);
++nr;
}
}io ;
int fr , ba ;
int val( Data x , int now ){
return x.val + sum[now][now] - sum[x.ed][now] ;
}
int cnt ;
int better( Data x , Data y ){
int lf = x.ed , rg = N , rt = N + 1 , mid ;
while( lf <= rg ){
mid = ( lf + rg ) >> 1 ;
int t1 = val( x , mid ) , t2 = val( y , mid ) ;
if( t1 < t2 || ( t1 == t2 && x.cnt < y.cnt ) )
rt = mid , rg = mid - 1 ;
else lf = mid + 1 ;
} return rt ;
}
inline void Push( Data x ){
while( ba > fr ){
int t1 = better( que[ba] , que[ba-1] ) , t2 = better( x , que[ba] ) ;
if( t1 > t2 || ( t1 == t2 && x.cnt <= que[ba].cnt ) ) ba -- ;
else break ;
} que[++ba] = x ;
}
Data calc( int cost ){
fr = 1 , ba = 0 , Push( Data( 0 , 0 , 0 ) ) ;
for( int i = 1 ; i <= N ; i ++ ){
while( ba > fr ){
int t1 = val( que[fr] , i ) , t2 = val( que[fr+1] , i ) ;
if( t1 > t2 || ( t1 == t2 && que[fr].cnt > que[fr+1].cnt ) ) fr ++ ;
else break ;
} dp[i] = Data( val( que[fr] , i ) + cost , i , que[fr].cnt + 1 ) ;
Push( dp[i] ) ;
} return dp[N] ;
}
void solve(){
int lf = 0 , rg = 2000 * 2000 * 10 , ans ;
while( lf <= rg ){
int mid = ( lf + rg ) >> 1 ;
Data res = calc( mid ) ;
if( res.cnt == K )
return ( void )printf( "%d\n" , res.val - K * mid ) ;
if( res.cnt > K ) lf = mid + 1 ;
else ans = mid , rg = mid - 1 ;
} Data res = calc( ans ) ;
printf( "%d\n" , res.val - K * ans ) ;
}
void read_( int &x ){
register char ch = getchar() ;
while( ch < '0' || ch > '9' ) ch = getchar() ;
while( ch >='0' && ch <='9' ) x = ( x << 1 ) + ( x << 3 ) + ch - 48 , ch = getchar() ;
}
int main(){
N = io.read() ; K = io.read() ;
for( int i = 1 ; i <= N ; i ++ ){
int *t1 = sum[i] , *t2 = sum[i-1] ;
for( int j = 1 ; j <= N ; j ++ ){
t1[j] = io.read() ;
if( j <= i ) t1[j] = t2[j] + t1[j-1] - t2[j-1] ;
else t1[j] += t2[j] + t1[j-1] - t2[j-1] ;
}
} solve() ;
}