划分sol

划分

  • 很好的dp优化题
  • 考虑最简单的dp,设f[i][j]表示上一位置为j,当前位置为i,j+1~i为划分的一段,得到的最小值
  • 这样可以的到36分,(O(n^3))
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define SZ(x) ((int)x.size())
#define ALL(x) x.begin(),x.end()
#define U(i,u) for(register int i=head[u];i;i=nxt[i])
#define rep(i,a,b) for(register int i=(a);i<=(b);++i)
#define per(i,a,b) for(register int i=(a);i>=(b);--i)
using namespace std;
typedef long double ld;
typedef long long ll;
typedef unsigned int ui;
typedef pair<int,int> PII;
typedef vector<int> VI;
template<class T> inline void read(T &x){
	x=0;char c=getchar();int f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=x*10+c-'0';c=getchar();}x*=f;
}
template<class T> inline void cmin(T &x, T y){x=x>y?y:x;}
const int N=5001;
const ll INF=100000000000000010;
int n,m;
ll a[N],f[N][N],sum[N],ans=INF;
int main(){
	read(n);read(m);rep(i,1,n)read(a[i]),sum[i]=sum[i-1]+a[i];sum[0]=0;
	rep(i,0,n)rep(j,0,n)f[i][j]=INF;f[0][0]=0;
	rep(i,1,n){
		rep(j,0,i-1){
			rep(k,0,j){
				if(k==j&&j!=0)continue;
				if(sum[i]-sum[j]>=sum[j]-sum[k])
					cmin(f[i][j],f[j][k]+(sum[i]-sum[j])*(sum[i]-sum[j]));
				if(i==n)cmin(ans,f[i][j]);
			}
		}
	}
	printf("%lld",ans);
	return 0;
}
  • 考虑如果中间一维j是固定的,在i递减的时候,k是递增的,那么维护f[j][k]的最小值进行转移,同时将j这一维提到外面,i这一维倒序做,k递增
  • 这样就可以去掉k这一维,做到(O(n^2))
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define SZ(x) ((int)x.size())
#define ALL(x) x.begin(),x.end()
#define U(i,u) for(register int i=head[u];i;i=nxt[i])
#define rep(i,a,b) for(register int i=(a);i<=(b);++i)
#define per(i,a,b) for(register int i=(a);i>=(b);--i)
using namespace std;
typedef long double ld;
typedef long long ll;
typedef unsigned int ui;
typedef pair<int,int> PII;
typedef vector<int> VI;
template<class T> inline void read(T &x){
	x=0;char c=getchar();int f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=x*10+c-'0';c=getchar();}x*=f;
}
template<class T> inline void cmin(T &x, T y){x=x>y?y:x;}
const int N=5001;
const ll INF=100000000000000010;
int n,m;
ll a[N],f[N][N],sum[N],ans=INF;
int main(){
	read(n);read(m);rep(i,1,n)read(a[i]),sum[i]=sum[i-1]+a[i];sum[0]=0;
	rep(i,0,n)rep(j,0,n)f[i][j]=INF;f[0][0]=0;
	rep(i,1,n)f[i][0]=sum[i]*sum[i];
	rep(j,1,n-1){
		int pos=0,k=0;
		ll mx=INF;
		per(i,n,j+1){
			while(sum[j]-sum[k]>sum[i]-sum[j]&&k<j-1)++k;
			if(sum[j]-sum[k]>sum[i]-sum[j])break;
			pos=max(k,pos);while(pos<j){
				if(f[j][pos]<=f[j][k])k=pos;
				++pos;
			}
			if(sum[j]-sum[k]<=sum[i]-sum[j])cmin(f[i][j],f[j][k]+(sum[i]-sum[j])*(sum[i]-sum[j]));
		}	
	}
	rep(i,0,n)cmin(ans,f[n][i]);
	printf("%lld",ans);
	return 0;
}
  • 通过打表,其实挺明显,f[i][1...n]是递减的,所以贪心考虑要求最后被划分的一段尽量小
  • 用一个单调队列维护d(i)表示i这一位置贪心转移后最后一段大小,并且在决策的时候要尽量取i大的
  • 同时dp数组也可以省一维了,直接贪心转移,复杂度(O(n)),可以得到88分
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define SZ(x) ((int)x.size())
#define ALL(x) x.begin(),x.end()
#define U(i,u) for(register int i=head[u];i;i=nxt[i])
#define rep(i,a,b) for(register int i=(a);i<=(b);++i)
#define per(i,a,b) for(register int i=(a);i>=(b);--i)
using namespace std;
typedef long double ld;
typedef long long ll;
typedef unsigned int ui;
typedef pair<int,int> PII;
typedef vector<int> VI;
template<class T> inline void read(T &x){
	x=0;char c=getchar();int f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=x*10+c-'0';c=getchar();}x*=f;
}
template<class T> inline void cmin(T &x, T y){x=x>y?y:x;}
const int N=4e7+10;
int n,m,l,r;
ll a[N],f[N],su[N],d[N],q[N];
int main(){
	read(n);read(m);rep(i,1,n)read(a[i]),su[i]=su[i-1]+a[i];
	l=1,r=1;q[1]=0;//单调队列要考虑保留0这个决策
	rep(i,1,n){
		while(l<r&&d[q[l+1]]+su[q[l+1]]<=su[i])++l;//只要更右边有合理决策,就会往右边走,使得d[i]尽量小
		d[i]=su[i]-su[q[l]];f[i]=f[q[l]]+d[i]*d[i];
		while(l<r&&d[q[r]]+su[q[r]]>=d[i]+su[i])--r;
		q[++r]=i;
	}
	printf("%lld",f[n]);
	return 0;
}
  • 最后3个点可以考虑高精度或者__int128
  • 用__int128的话同时要优化空间,记录每个点是从哪个点转移过来,在最后计算即可,得到100分
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define SZ(x) ((int)x.size())
#define ALL(x) x.begin(),x.end()
#define U(i,u) for(register int i=head[u];i;i=nxt[i])
#define rep(i,a,b) for(register int i=(a);i<=(b);++i)
#define per(i,a,b) for(register int i=(a);i>=(b);--i)
using namespace std;
typedef long double ld;
typedef __int128 ll;
typedef unsigned int ui;
typedef pair<int,int> PII;
typedef vector<int> VI;
template<class T> inline void read(T &x){
	x=0;char c=getchar();ll f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=x*10+c-'0';c=getchar();}x*=f;
}
template <typename T> inline void print(T x) {if(x<0)putchar('-'),x =-x;if(x < 10)putchar(x + 48);else print(x/10),putchar(x%10+48);}
template<class T> inline void cmin(T &x, T y){x=x>y?y:x;}
const int N=4e7+10;
const int M=1e5+10;	
const int P=(1<<30);
int n,opt,l,r,pre[N],q[N];
long long su[N];
int p2[M],l2[M],r2[M],b[N];
long long d(int x){return su[x]-su[pre[x]];}
int main(){
	read(n);read(opt);
	if(opt){
		int x,y,z,m;read(x);read(y);read(z);read(b[1]);read(b[2]);read(m);rep(i,1,m)read(p2[i]),read(l2[i]),read(r2[i]);
		rep(i,3,n)b[i]=(0ll+1ll*x*b[i-1]+1ll*y*b[i-2]+z)%P;rep(i,1,m)rep(j,p2[i-1]+1,p2[i])su[j]=b[j]%(r2[i]-l2[i]+1)+l2[i]+su[j-1];
		l=1,r=1;q[1]=0;//单调队列要考虑保留0这个决策
		rep(i,1,n){
			while(l<r&&d(q[l+1])+su[q[l+1]]<=su[i])++l;//只要更右边有合法决策,就会往右边走,使得d[i]尽量小
			pre[i]=q[l];
			while(l<r&&d(q[r])+su[q[r]]>=d(i)+su[i])--r;//单调队列维护了d[i]+su[i]
			q[++r]=i;
		}
		ll ans=0,tmp;
		int p=n;while(p){tmp=d(p);tmp*=d(p);ans+=tmp;p=pre[p];}print(ans);
	}else{
		int x;rep(i,1,n)read(x),su[i]=su[i-1]+x;l=1,r=1;q[1]=0;//单调队列要考虑保留0这个决策
		rep(i,1,n){
			while(l<r&&d(q[l+1])+su[q[l+1]]<=su[i])++l;//只要更右边有合法决策,就会往右边走,使得d[i]尽量小
			pre[i]=q[l];
			while(l<r&&d(q[r])+su[q[r]]>=d(i)+su[i])--r;//单调队列维护了d[i]+su[i]
			q[++r]=i;
		}
		ll ans=0,tmp;
		int p=n;while(p){tmp=d(p);tmp*=d(p);ans+=tmp;p=pre[p];}print(ans);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/hangzz/p/13405711.html