把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

luogu P4767 [IOI2000]邮局

题面传送门
首先这个是2000年的IOI所以没有什么wqs二分。
\(dp_{i,j}\)为到了\(i\),已经有了\(j\)个邮局的最小距离和。
显然有方程式\(dp_{i,j}=\min_{k=1}^{i-1}{dp_{k,j-1}+w(k+1,i)}\),其中\(w(i,j)\)为在\([i,j]\)区间内村庄建立一个邮局的最小距离和。由初一数学可知这个一定是在中位数。
有人要问了:不是说每个村庄距离其最近的邮局吗,并没有考虑最近啊。
因为这个是最优性所以可以直接不用考虑的。
这样子是\(40\)分的\(O(V^3)\)
考虑优化,感性理解一下这个东西满足决策单调性,所以可以直接上了。
时间复杂度\(O(VPlogV)\)
code:

#include<cstdio>
#include<cstring>
#define max(a,b) ((a)>(b)?(a):(b)) 
#define N 3039
#define V 339
#define I inline
using namespace std;
int n,m,v,x,y,z,head,tail,a[N],dp[V][N],sum[N],l,r,mid;
I int calc(int x,int y){return sum[y]+sum[x-1]-sum[x+y>>1]-sum[x+y-1>>1];}
struct ques{int l,r,id;}q[N];
int main(){
	freopen("1.in","r",stdin);
	register int i,j,h;scanf("%d%d",&n,&v);memset(dp,0x3f,sizeof(dp));dp[0][0]=0;
	for(i=1;i<=n;i++) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
	for(i=1;i<=v;i++){
		q[head=tail=0]=(ques){1,n,0};
		for(j=1;j<=n;j++){
			while(head<=tail&&q[head].r<j) head++;h=q[head].id;dp[i][j]=dp[i-1][h]+calc(h+1,j);
			while(head<=tail&&(q[tail].l=max(q[tail].l,j))&&dp[i-1][q[tail].id]+calc(q[tail].id+1,q[tail].l)>=dp[i-1][j]+calc(j+1,q[tail].l)) tail--;
			if(head>tail){q[++tail]=(ques){j,n,j};continue;}l=q[tail].l;r=q[tail].r+1;
			while(l+1<r)mid=l+r>>1,dp[i-1][q[tail].id]+calc(q[tail].id+1,mid)<dp[i-1][j]+calc(j+1,mid)?(l=mid):(r=mid);
			q[tail].r=l;if(r<=n) q[++tail]=(ques){r,n,j};
		}
	}
	printf("%d\n",dp[v][n]);
}
posted @ 2021-04-17 20:52  275307894a  阅读(36)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end