关于决策单调性

四边形不等式与决策单调性

有如下的定理:

如果对于 \(a<b\),有 \(w(a,b+1)+w(a+1,b)>=w(a+1,b+1)+w(a,b)\) 成立,则此函数满足四边形不等式。

对于方程:

\[dp[𝑖][𝑗]=min⁡(dp[𝑖][𝑘]+dp[𝑘+1][𝑗]) \]

定义使 \(dp[𝑖][𝑗]\) 取到最优值的 \(𝑘\) 称为 \(dp[𝑖][𝑗]\) 决策点,记作 \(𝑠[𝑖][𝑗]\)

\(dp[𝑖][𝑗]\) 满足四边形不等式则 \(𝑠[𝑖][𝑗−1]≤s[𝑖][𝑗]≤𝑠[𝑖+1][𝑗]\)

这样的话,在求 \(s[i][j]\) 时只需从 \(s[i][j-1]\) 循环到 \(s[i+1][j]\)

枚举长度 \(L\),总时间为:

\(∑_{𝑖=1}^{𝑛−𝐿+1}\limits (𝑠[𝑖+1][𝑖+𝐿−1]−𝑠[𝑖][𝑖+𝐿−2]) =𝑠[𝑛−𝐿+2][𝑛]−𝑠[1][𝐿−1]=𝑂(𝑛)\)

故总时间复杂度为 \(O(𝑛^2)\)

[IOI2013]wombats

对于决策单调性,可以用四边形不等式证明,实际中更常用的方法是感性理解以及打表找规律

本题中,可以发现最优路径不会交叉,因此利用上面的方式将 \(Floyd\)\(O(n^3)\) 优化到 \(O(n^2)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int R,C,E;
int h[5005][205],v[5005][205],Loc[205][205];
struct mat{
	int a[205][205];
	inline void init(int _x=1e9){
		for(int i=0;i<=R+1;i++){
			for(int j=0;j<=R+1;j++)a[i][j]=1e9;
			a[i][i]=_x;
		}
	}
	inline int* operator [](int t){
		return a[t];
	}
	inline mat operator *(mat b){
		mat res;res.init();
		for(int i=0;i<=R+1;i++){
			for(int j=0;j<=R+1;j++)Loc[i][j]=0;
		}
		for(int i=1;i<=R;i++){
			for(int j=R;j>=1;j--){
				int l=(1,Loc[i-1][j]?Loc[i-1][j]:1),r=(Loc[i][j+1]?Loc[i][j+1]:R);
				for(int t=l;t<=r;t++){
     				if(res[i][j]>a[i][t]+b[t][j]){
						res[i][j]=a[i][t]+b[t][j];
						Loc[i][j]=t;
					}
				}
			}
		}return res;
	}
}tree[805];
inline void calc(int l,int r,mat &mp){
	mp.init(0);
	for(int i=l;i<=r;i++){
		for(int j=1;j<=R;j++){
			for(int t=1;t<=R;t++)mp[j][t]+=v[i][t];
		}
		for(int j=1;j<=R;j++){
			for(int t=1;t<=R;t++)mp[j][t]=min(mp[j][t],mp[j][t-1]+h[i][t-1]);
			for(int t=R;t>=1;t--)mp[j][t]=min(mp[j][t],mp[j][t+1]+h[i][t]);
		}
	}
}
void build(int l=1,int r=C,int i=1){
	if(r-l<25){
		calc(l,r,tree[i]);return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,i<<1);build(mid+1,r,i<<1|1);
	tree[i]=tree[i<<1]*tree[i<<1|1];
}
void update(int loc,int l=1,int r=C,int i=1){
	if(loc<l||loc>r)return ;
	if(r-l<25){
		calc(l,r,tree[i]);return ;
	}
	int mid=(l+r)>>1;
	update(loc,l,mid,i<<1);update(loc,mid+1,r,i<<1|1);
	tree[i]=tree[i<<1]*tree[i<<1|1];
}
int main(){
	scanf("%d%d",&C,&R);
	for(int i=1;i<=C;i++){
		for(int j=1;j<R;j++)scanf("%d",&h[i][j]);
	}
	for(int i=2;i<=C;i++){
		for(int j=1;j<=R;j++)scanf("%d",&v[i][j]);
	}
	build();
	scanf("%d",&E);
	while(E--){
		int op,x,y,z;
		scanf("%d",&op);
		if(op==1){
			scanf("%d%d%d",&x,&y,&z);x++;y++;
			h[x][y]=z;update(x);
		}
		else if(op==2){
			scanf("%d%d%d",&x,&y,&z);x+=2;y++;
			v[x][y]=z;update(x);
		}
		else if(op==3){
			scanf("%d%d",&x,&y);x++;y++;
			printf("%d\n",tree[1][x][y]);
		}
	}
	

	return 0;
}

[IOI2000]邮局

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int v,p;
int dp[3005][305],w[3005][3005];
int a[3005],loc[3005][305];
inline void init(){
	for(int i=1;i<=v;i++){
		for(int j=i;j<=v;j++)w[i][j]=w[i][j-1]+a[j]-a[(i+j)>>1];
	}
}
int main(){
	scanf("%d%d",&v,&p);
	for(int i=1;i<=v;i++)scanf("%d",&a[i]);
	sort(a+1,a+v+1);init();
	memset(dp,0x3f,sizeof(dp));dp[0][0]=0;
	for(int i=1;i<=v;i++){
		for(int j=p;j;j--){
			int l=(loc[i-1][j]?loc[i-1][j]:1),r=(loc[i][j+1]?loc[i][j+1]:i);
			for(int t=l;t<=r;t++){
				if(dp[i][j]>dp[t-1][j-1]+w[t][i]){
					dp[i][j]=dp[t-1][j-1]+w[t][i];
					loc[i][j]=t;
				}
			}
		}
	}printf("%d",dp[v][p]);

	return 0;
}

分治优化 \(dp\)

\(calc(l,r,L,R)\) 表示要求的区间为 \([l,r]\),决策区间为 \([L,R]\),每次选 \(l,r\) 中点 \(mid\) 暴力求解其决策点 \(p\)

然后递归 \(calc(l,mid-1,L,p)\)\(calc(mid+1,r,p,R)\)
递归 \(log\) 层,每层暴力求 \(p\) 的复杂度之和为 \(n\),故总复杂度为\(𝑂(𝑛 log ⁡𝑛)\)

CF321E Ciel and Gondolas

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,k;
void Read( int &x ) {
	x = 0; int f = 1;
	char s = getchar( );
	for( ; s < '0' || s > '9' ; s = getchar( ) ) f = s == '-' ? -f : f;
	for( ; s >= '0' && s <= '9' ; s = getchar( ) ) x = x * 10 + s - '0';
	x *= f;
}
int a[4005][4005];
long long sum[4005][4005],dp[805][4005];
void solve(int lim,int l=1,int r=n,int ql=0,int qr=n){
	if(l>r)return ;
	int mid=(l+r)>>1,loc=0;
	for(int i=ql;i<=mid&&i<=qr;i++){
		if(dp[lim][mid]>dp[lim-1][i]+sum[i+1][mid]){
			dp[lim][mid]=dp[lim-1][i]+sum[i+1][mid];
			loc=i;
		}
	}
	solve(lim,l,mid-1,ql,loc);solve(lim,mid+1,r,loc,qr);
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++)Read(a[i][j]);
	}
	for(int i=1;i<=n;i++)sum[i][i]=a[i][i];
	for(int len=1;len<=n;len++){
		for(int i=1;i+len<=n;i++){
			int j=i+len;
			sum[i][j]=sum[i][j-1]+sum[i+1][j]+a[i][j]+a[j][i]-sum[i+1][j-1];
		}
	}
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
	for(int i=1;i<=k;i++)solve(i);
	printf("%lld",dp[k][n]/2);

	return 0;
}


CF868F Yet Another Minimization Problem

点击查看代码 ```cpp #include using namespace std; int n,k; int a[100005],cnt[100005],L,R; long long tot; inline long long calc(int l,int r){ while(Ll)tot+=cnt[a[--L]]++; while(R>r)tot-=--cnt[a[R--]]; while(Rqr||l>r)return ; int mid=(l+r)>>1,res; for(int i=ql;i<=qr;i++){ long long tmp=calc(i+1,mid); if(dp[top][mid]>dp[top-1][i]+tmp){ res=i;dp[top][mid]=dp[top-1][i]+tmp; } } solve(ql,res,l,mid-1);solve(res,qr,mid+1,r); } int main(){ scanf("%d%d",&n,&k); memset(dp,0x3f,sizeof(dp)); for(int i=1;i<=n;i++)scanf("%d",&a[i]); dp[0][0]=0;cnt[a[1]]++;L=R=1; while(++top<=k)solve(); printf("%lld",dp[k][n]);
return 0;

}

</details>

### [[IOI2014]holiday 假期](https://www.luogu.com.cn/problem/P5892 "[IOI2014]holiday 假期")

<details>
<summary>点击查看代码</summary>
```cpp
#include<bits/stdc++.h>
using namespace std;
int n,s,d;
int a[100005];
int rt[100005],tree[4000005],le[4000005],ri[4000005],cnt;
long long sum[4000005];
void insert(int old,int &i,int loc,int l=0,int r=1e9+1){
	if(loc<l||loc>r)return ;
	i=++cnt;sum[i]=sum[old]+loc;le[i]=le[old];ri[i]=ri[old];tree[i]=tree[old]+1;
	if(l==r)return ;
	int mid=(1ll*l+r)>>1;
	insert(le[old],le[i],loc,l,mid);insert(ri[old],ri[i],loc,mid+1,r);
}
long long query(int a,int b,int k,int l=0,int r=1e9+1){
	if(k<=0)return 0;
	if(k>=tree[a]-tree[b])return sum[a]-sum[b];
	if(l==r)return 1ll*k*l;
	int mid=(1ll*l+r)>>1;
	if(tree[ri[a]]-tree[ri[b]]>=k)return query(ri[a],ri[b],k,mid+1,r);
	return query(le[a],le[b],k-tree[ri[a]]+tree[ri[b]],l,mid)+sum[ri[a]]-sum[ri[b]];
}
inline long long calc1(int l,int r){
	int k=d-r+l-r+s;
	return query(rt[r],rt[l-1],k);
}
inline long long calc2(int l,int r){
	int k=d-r+l-s+l;
	return query(rt[r],rt[l-1],k);
}
long long ans;
void solve(long long calc(int,int),int l=1,int r=s,int ql=s,int qr=n){
	if(l>r)return ;
	int mid=(l+r)>>1,loc=ql;
	long long res=calc(mid,ql);
	for(int i=ql;i<=qr;i++){
		long long tmp=calc(mid,i);ans=max(ans,tmp);
		if(tmp>res){
			res=tmp;loc=i;
		}
	}
	solve(calc,l,mid-1,ql,loc);solve(calc,mid+1,r,loc,qr);
}
int main(){
	scanf("%d%d%d",&n,&s,&d);s++;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		insert(rt[i-1],rt[i],a[i]);
	}
	solve(calc1);solve(calc2);
	printf("%lld",ans);

	return 0;
}




单调栈优化 \(dp\)

在单调栈中保存三元组 \((p,l,r)\) 表示 \([l,r]\) 这些位置的决策是 \(p\),初始时保存 \((0,1,n)\),每处理一个位置,这个位置就可以作为决策。

计算单调栈中哪些决策应修改为当前位置,这些位置一定是一个后缀,如果某一个三元组左端点还不如新的决策优,那么整个三元组都可以删去。然后再在某个三元组上二分,得到决策区间,将这个三元组留下前半部分并加入这次的位置的新三元组。

[NOI2009] 诗人小G

点击查看代码
#include<bits/stdc++.h>
using namespace std;
long long t,n,l,p;
long long sum[100005];
long double dp[100005];
int ql[100005],qr[109005],top,ed;
int pre[100005];
int stk[100005],tp;
char s[100005][33];
inline long double ksm(long double a,long long b){
	long double res=1;
	while(b){
		if(b&1)res*=a;
		a*=a;b>>=1;
	}
	return res;
}
inline long double calc(int i,int j){
	return dp[j]+ksm(abs(sum[i]-sum[j]-1-l),p);
}
inline int bound(int i,int j,int x){
	int l=x,r=n+1;
	while(l<=r){
		int mid=(l+r)>>1;
		if(calc(mid,i)<=calc(mid,j))r=mid-1;
		else l=mid+1;
	}
	return l;
}
int main(){
	scanf("%lld",&t);
	while(t--){
		scanf("%lld%lld%lld",&n,&l,&p);
		for(int i=1;i<=n;i++){
			scanf("%s",s[i]);
			sum[i]=sum[i-1]+strlen(s[i])+1;
		}
		tp=top=ed=0;qr[top]=n;
		for(int i=1;i<=n;i++){
			while(top<ed&&qr[top]<=i)top++;
			dp[i]=calc(i,ql[top]);pre[i]=ql[top];
			while(top<ed&&qr[ed-1]>=bound(i,ql[ed],qr[ed-2]+1))ed--;
			qr[ed]=bound(i,ql[ed],qr[ed-1]+1);ql[++ed]=i;
		}
		if(dp[n]>1e18){
			puts("Too hard to arrange");
            puts("--------------------");
			continue;
		}
		printf("%.0Lf\n",dp[n]);
		for(int i=stk[tp]=n;i;stk[++tp]=i=pre[i]);
		for(int i=tp;i;i--){
			for(int j=stk[i]+1;j<=stk[i-1];j++){
				if(j!=stk[i-1])printf("%s ",s[j]);
				else printf("%s\n",s[j]);
			}
		}
        puts("--------------------");
	}
	
	
    return 0;
}

[IOI2000] 邮局 加强版

点击查看代码 ```cpp #include using namespace std; struct data{ int x,k; data(int u=0,int v=0){ x=u,k=v; } }q[500010]; long long dp[500010],pr[500010]; int s[500010],a[500010]; int n,m,hd,tl; inline long long calc(int l,int r){ int pos=(l+r+1)>>1; return (pr[r]-pr[pos])-a[pos]*1ll*(r-pos)+a[pos]*1ll*(pos-l)-(pr[pos]-pr[l]); } inline int find(int i,int j){ int l=j,r=n+1; while(l>1; if(dp[i]+calc(i,mid)=m; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",&a[i]); sort(a+1,a+n+1); for(int i=1;i<=n;i++)pr[i]=pr[i-1]+a[i]; long long l=0,r=5e11; while(l>1; if(check(mid)) l=mid; else r=mid; } check(l); printf("%lld",dp[n]-m*l); return 0; } ```
posted @ 2022-01-03 12:06  一粒夸克  阅读(216)  评论(0编辑  收藏  举报