csacademy Squared Ends 题解(动态凸包/李超树/分治/二进制技巧

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;
}
上一篇:LeetCode刷题记81-71. 简化路径


下一篇:在一个新线程(_beginthreadex)中调用 COM 库 Excel.Application 很受伤