2018.11.02【校内模拟】距离(斜率优化DP)

传送门


解析:

优秀的斜率优化DP。

首先DP式子不是很好想,再加上本蒟蒻很久没写斜率优化,这道题就场上直接咕咕咕了。。。

思路:

不要想一次四个方向处理完(不然下场和我一样,无法处理后效性),我们可以处理四次,分别处理左上右下右上左下,最后所有答案取一个 m i n min min就可以了。

那么考虑怎么处理左上方向,其他的显然可以通过对称转变一下。

考虑我们已经处理到第 i i i列,我们可以维护前 i − 1 i-1 i1行每一行平移过来最近的 1 1 1。第 j j j行记为 l a s t 1 j last1_j last1j,所以我们的决策就是 f i = min ⁡ j = 1 i − 1 { l a s t j 2 + ( i − j ) 2 } f_{i}=\min_{j=1}^{i-1}\{last_j^2+(i-j)^2\} fi=j=1mini1{lastj2+(ij)2}

这个东西可以斜率优化一下,即 f i = i 2 + min ⁡ j = 1 i − 1 { l a s t j 2 + j 2 − 2 i j } f_i=i^2+\min_{j=1}^{i-1}\{last_j^2+j^2-2ij\} fi=i2+j=1mini1{lastj2+j22ij}

考虑斜率优化,另 k 1 &lt; k 2 &lt; i k_1&lt;k_2&lt;i k1<k2<i k 2 k_2 k2是较优决策则有 i 2 + l a s t k 1 2 + k 1 2 − 2 i k 1 ≥ i 2 + l a s t k 2 2 + k 2 2 − 2 i k 2 i^2+last_{k_1}^2+k_1^2-2ik_1\geq i^2+last_{k_2}^2+k_2^2-2ik_2 i2+lastk12+k122ik1i2+lastk22+k222ik2
2 i ≥ ( l a s t k 2 2 + k 2 2 ) − ( l a s t k 1 2 + k 1 2 ) k 2 − k 1 2i\geq\frac{(last_{k_2}^2+k_2^2)-(last_{k_1}^2+k_1^2)}{k_2-k_1} 2ik2k1(lastk22+k22)(lastk12+k12)

然后斜率优化乱搞就行了。
然后发现是 2 i 2i 2i单调的,所以可以直接来单调队列维护下凸壳就行了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

cs int N=1003;
int n,m;
bool way[N][N];
int ans[N][N];
int f[N][N];
int last1[N];

struct Point{
	int x,y;
	Point(cs int &_x=0,cs int &_y=0):x(_x),y(_y){}
	friend ll operator*(cs Point &a,cs Point &b){return 1ll*a.x*b.y-1ll*a.y*b.x;}
	friend Point operator-(cs Point &a,cs Point &b){
		return Point(a.x-b.x,a.y-b.y);
	}
}p[N];

inline void solve(){
	fill(last1+1,last1+m+1,N<<1);
	for(int re i=1;i<=n;++i){
		int head=1,tail=0;
		for(int re j=1;j<=m;++j){
			++last1[j];
			if(way[i][j])last1[j]=0;
			
			Point tmp=Point(j,last1[j]*last1[j]+j*j);
			while(head<tail&&(p[tail]-p[tail-1])*(tmp-p[tail-1])<=0)--tail;
			p[++tail]=tmp;
			while(head<tail&&p[head].y-2*j*p[head].x>p[head+1].y-2*j*p[head+1].x)++head;
			f[i][j]=p[head].y-2*j*p[head].x+j*j;
		}
	}
}

signed main(){
	ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int re i=1;i<=n;++i){
		string sss;
		cin>>sss;
		for(int re j=1;j<=m;++j)way[i][j]=sss[j-1]^48;
	}
	
	solve();
	for(int re i=1;i<=n;++i)
	for(int re j=1;j<=m;++j)ans[i][j]=f[i][j];
	
	for(int re i=1;i<=n;++i)reverse(way[i]+1,way[i]+m+1);
	solve();
	for(int re i=1;i<=n;++i)
	for(int re j=1;j<=m;++j)ans[i][j]=min(ans[i][j],f[i][m-j+1]);
	
	for(int re i=1;i*2<=n;++i)swap(way[i],way[n-i+1]);
	//交换数组的swap和交换变量的swap不是同一个,有不同的声明和实现。
	solve();
	for(int re i=1;i<=n;++i)
	for(int re j=1;j<=m;++j)ans[i][j]=min(ans[i][j],f[n-i+1][m-j+1]);
	
	for(int re i=1;i<=n;++i)reverse(way[i]+1,way[i]+m+1);
	solve();
	for(int re i=1;i<=n;++i)
	for(int re j=1;j<=m;++j)ans[i][j]=min(ans[i][j],f[n-i+1][j]);
	
	for(int re i=1;i<=n;++i){
		for(int re j=1;j<=m;++j)
		printf("%d ",ans[i][j]);
		pc('\n');
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值