csacademy Squared Ends 题解(动态凸包/李超树/分治/二进制技巧)
首先这是一个比较明显的dp题:
\(dp_{i,j}\)表示前i个分成j段的最优解。
\[dp_{i,j}=\min(dp_{k,j}+(a_{k+1}-a_i)^2)=\min (dp_{k,j}+a_{k+1}^2-2\times a_{k+1}\times a_i)+a_i^2 \]但这样时间复杂度为\(O(n^2\times m)\),会TLE。
解法1: 动态凸包/李超树
\(dp_{k,j}+a_{k+1}^2-2\times a_{k+1}\times a_i\)这个东西实际上可以看作一个函数:\(f_k(x)=-2\times a_{k+1}\times x+dp_{k,j}+a_{k+1}^2\)
这样原问题就是在一堆函数中找到在\(a_i\)处取得最小值的函数,这样就是李超树/动态凸包的板子了。但是用李超树的话对\(a_i\)的值域有一定要求,否则需要动态开点线段树。时间复杂度\(O(n\times \log n)\)。
/*
{
######################
# Author #
# Gary #
# 2020 #
######################
*/
//#pragma GCC target ("avx2")
//#pragma GCC optimization ("O3")
//#pragma GCC optimization ("unroll-loops")
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rb(a,b,c) for(int a=b;a<=c;++a)
#define rl(a,b,c) for(int a=b;a>=c;--a)
#define LL long long
#define IT iterator
#define PB push_back
#define II(a,b) make_pair(a,b)
#define FIR first
#define SEC second
#define FREO freopen("check.out","w",stdout)
#define rep(a,b) for(int a=0;a<b;++a)
#define SRAND mt19937 rng(chrono::steady_clock::now().time_since_epoch().count())
#define random(a) rng()%a
#define ALL(a) a.begin(),a.end()
#define POB pop_back
#define ff fflush(stdout)
#define fastio ios::sync_with_stdio(false)
#define check_min(a,b) a=min(a,b)
#define check_max(a,b) a=max(a,b)
using namespace std;
const int INF=0x3f3f3f3f;
typedef pair<int,int> mp;
/*}
*/
int n;
struct line{
LL k,b;
line (){
k=0;
b=1e18;
}
line (LL A,LL B){
k=A;
b=B;
}
};
const int N=1<<20;
LL get(LL x,line L){
return L.k*x+L.b;
}
struct LiChao_Tree{
line tree[N+N];
void init(){
rb(i,1,N+N-1)
{
tree[i].k=0;
tree[i].b=1e18;
}
}
void modify(line L,int now=1,int l=1,int r=N+1){
line tmp=tree[now];
int mid=(l+r)>>1;
if(l==r-1){
if(get(mid,tree[now])>get(mid,L)){tree[now]=L;}
return;
}
if(tree[now].k<L.k){
if(get(mid,L)<get(mid,tree[now])){
tree[now]=L;
modify(tmp,now<<1|1,mid,r);
}
else{
modify(L,now<<1,l,mid);
}
}
else{
if(tree[now].k>L.k){
if(get(mid,L)<get(mid,tree[now])){
tree[now]=L;
modify(tmp,now<<1,l,mid);
}
else{
modify(L,now<<1|1,mid,r);
}
}
else{
if(tree[now].b>L.b) tree[now]=L;
}
}
}
LL query(int x){
int xx=x;
x+=N-1;
LL rest=1e18;
while(x){
check_min(rest,get(xx,tree[x]));
x>>=1;
}
return rest;
}
}tree;
int a[10000+1];
LL dp[10000+1][2];
int main(){
scanf("%d",&n);
int k=0;
scanf("%d",&k);
rb(i,1,n){
scanf("%d",&a[i]);
}
memset(dp,127,sizeof(dp));
dp[0][0]=0;
rb(j,1,k)
{
tree.init();
rb(i,0,n)
dp[i][j&1]=1e18;
rb(i,1,n){
if(dp[i-1][(j&1)^1]<=1e15){
tree.modify(line(-2ll*a[i],1ll*a[i]*a[i]+dp[i-1][(j&1)^1]));
}
dp[i][j&1]=tree.query(a[i])+1ll*a[i]*a[i];
}
}
cout<<dp[n][k&1]<<endl;
return 0;
}
解法2:分治
可以发现\(dp_{*,j}\)只会对\(dp_{*,j+1}\)产生影响,所以我们可以按照第二维一层一层的dp。
由于只有左边的会影响右边的,所以对于每一层我们可以做一次分治。
分治的时候只需要维护静态凸包就行了,将询问离线的话,会更加方便,用一个stack就搞定了。时间复杂度\(O(n\times \log^2n)\)
/*
{
######################
# Author #
# Gary #
# 2020 #
######################
*/
//#pragma GCC target ("avx2")
//#pragma GCC optimization ("O3")
//#pragma GCC optimization ("unroll-loops")
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rb(a,b,c) for(int a=b;a<=c;++a)
#define rl(a,b,c) for(int a=b;a>=c;--a)
#define LL long long
#define IT iterator
#define PB push_back
#define II(a,b) make_pair(a,b)
#define FIR first
#define SEC second
#define FREO freopen("check.out","w",stdout)
#define rep(a,b) for(int a=0;a<b;++a)
#define SRAND mt19937 rng(chrono::steady_clock::now().time_since_epoch().count())
#define random(a) rng()%a
#define ALL(a) a.begin(),a.end()
#define POB pop_back
#define ff fflush(stdout)
#define fastio ios::sync_with_stdio(false)
#define check_min(a,b) a=min(a,b)
#define check_max(a,b) a=max(a,b)
using namespace std;
const int INF=0x3f3f3f3f;
typedef pair<LL,LL> mp;
/*}
*/
const int N=1e4+20;
int a[N];
int n;
LL dp[N][101];
bool cross(mp A,mp B,mp C){
return 1.0*(A.SEC-C.SEC)*(B.FIR-A.FIR)<1.0*(A.SEC-B.SEC)*(C.FIR-A.FIR);
}
LL get(mp A,LL x){
return x*A.FIR+A.SEC;
}
void update(int j,int l,int r){
if(l>=r){
return ;
}
int mid=(l+r)>>1;
update(j,l,mid);
update(j,mid+1,r);
vector<mp> lines;
rb(i,l+1,mid+1){
if(dp[i-1][j-1]<=1e15)
lines.PB(II(-2ll*a[i],1ll*a[i]*a[i]+dp[i-1][j-1]));
}
if(lines.empty()) return;
sort(ALL(lines));
reverse(ALL(lines));
stack<mp> sta;
int next_=0;
for(auto it:lines){
next_++;
if(next_<lines.size()&&lines[next_].FIR==it.FIR) continue;
while(sta.size()>1){
mp nex,nexnex;
nex=sta.top();
sta.pop();
nexnex=sta.top();
if(cross(nexnex,nex,it)){
continue;
}
else{
sta.push(nex);
break;
}
}
sta.push(it);
}
vector<mp> queries;
rb(i,mid+1,r){
queries.PB(II(a[i],i));
}
sort(ALL(queries));
reverse(ALL(queries));
for(auto it:queries){
while(sta.size()>1){
mp now=sta.top();
sta.pop();
mp pre=sta.top();
if(get(now,it.FIR)>get(pre,it.FIR)){
continue;
}
else{
sta.push(now);
break;
}
}
check_min(dp[it.SEC][j],get(sta.top(),it.FIR)+it.FIR*it.FIR);
}
}
int main(){
scanf("%d",&n);
int k=0;
scanf("%d",&k);
rb(i,1,n){
scanf("%d",&a[i]);
}
memset(dp,127,sizeof(dp));
dp[0][0]=0;
rb(j,1,k)
{
update(j,0,n);
}
cout<<dp[n][k]<<endl;
return 0;
}
解法3:
二进制技巧。
当\(dp_{i,j-1}\)影响到\(dp_{i\prime,j}\)的时候。
类似枚举\(i\)和\(i\prime\)最后一个不一样的二进制位。
时间复杂度:\(O(n\times \log^2n)\)
/*
{
######################
# Author #
# Gary #
# 2020 #
######################
*/
//#pragma GCC target ("avx2")
//#pragma GCC optimization ("O3")
//#pragma GCC optimization ("unroll-loops")
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rb(a,b,c) for(int a=b;a<=c;++a)
#define rl(a,b,c) for(int a=b;a>=c;--a)
#define LL long long
#define IT iterator
#define PB push_back
#define II(a,b) make_pair(a,b)
#define FIR first
#define SEC second
#define FREO freopen("check.out","w",stdout)
#define rep(a,b) for(int a=0;a<b;++a)
#define SRAND mt19937 rng(chrono::steady_clock::now().time_since_epoch().count())
#define random(a) rng()%a
#define ALL(a) a.begin(),a.end()
#define POB pop_back
#define ff fflush(stdout)
#define fastio ios::sync_with_stdio(false)
#define check_min(a,b) a=min(a,b)
#define check_max(a,b) a=max(a,b)
using namespace std;
const int INF=0x3f3f3f3f;
typedef pair<LL,LL> mp;
/*}
*/
const int N=1e4+20;
int a[N];
int n;
LL dp[N][101];
bool cross(mp A,mp B,mp C){
return 1.0*(A.SEC-C.SEC)*(B.FIR-A.FIR)<1.0*(A.SEC-B.SEC)*(C.FIR-A.FIR);
}
LL get(mp A,LL x){
return x*A.FIR+A.SEC;
}
void update(int j,vector<mp> & line,vector<mp> &query){
if(line.empty()) return;
sort(ALL(line));
reverse(ALL(line));
stack<mp> sta;
int next_=0;
for(auto it:line){
next_++;
if(next_<line.size()&&line[next_].FIR==it.FIR) continue;
while(sta.size()>1){
mp nex,nexnex;
nex=sta.top();
sta.pop();
nexnex=sta.top();
if(cross(nexnex,nex,it)){
continue;
}
else{
sta.push(nex);
break;
}
}
sta.push(it);
}
sort(ALL(query));
reverse(ALL(query));
for(auto it:query){
while(sta.size()>1){
mp now=sta.top();
sta.pop();
mp pre=sta.top();
if(get(now,it.FIR)>get(pre,it.FIR)){
continue;
}
else{
sta.push(now);
break;
}
}
check_min(dp[it.SEC][j],get(sta.top(),it.FIR)+it.FIR*it.FIR);
}
}
int main(){
scanf("%d",&n);
int k=0;
scanf("%d",&k);
rb(i,1,n){
scanf("%d",&a[i]);
}
memset(dp,127,sizeof(dp));
dp[0][0]=0;
rb(j,1,k)
{
rb(i,1,n){
int lb=i^(i&(i-1));
vector<mp> line;
vector<mp> query;
for(int i2=i-lb+1;i2<=i;++i2){
if(dp[i2-1][j-1]<=1e15)
line.PB(II(-2ll*a[i2],1ll*a[i2]*a[i2]+dp[i2-1][j-1]));
}
for(int i2=i;i2<min(i+lb,n+1);++i2){
query.PB(II(a[i2],i2));
}
update(j,line,query);
}
}
cout<<dp[n][k]<<endl;
return 0;
}