zoukankan      html  css  js  c++  java
  • BZOJXXXX: [IOI2000]邮局——四边形不等式优化初探

    貌似$BZOJ$上并没有这个题。。。

    是嫌这个题水了么。。。

    还是要氪金权限号???

    这里附上洛谷的题面:洛谷P4767 [IOI2000]邮局

    题目描述

    高速公路旁边有一些村庄。高速公路表示为整数轴,每个村庄的位置用单个整数坐标标识。没有两个在同样地方的村庄。两个位置之间的距离是其整数坐标差的绝对值。

    邮局将建在一些,但不一定是所有的村庄中。为了建立邮局,应选择他们建造的位置,使每个村庄与其最近的邮局之间的距离总和最小。

    你要编写一个程序,已知村庄的位置和邮局的数量,计算每个村庄和最近的邮局之间所有距离的最小可能的总和。

    输入输出格式

    输入格式:

    第一行包含两个整数:第一个是村庄$V$的数量,第二个是邮局的数量$P$,$1 leq P leq 300$,$P leq V leq 3000$.

    第二行包含$V$个整数。这些整数是村庄的位置。对于每个位置$X$,认为$1 leq X leq 10000$

    输出格式:

    第一行包含一个整数$S$,它是每个村庄与其最近的邮局之间的所有距离的总和。

    输入输出样例

    输入样例#1: 复制
    10 5 
    1 2 3 6 7 9 11 22 44 50
    输出样例#1: 复制
    9

    说明

    对于40%的数据,$V leq 300$


    题解Here!

    首先这是个区间$DP$对吧。

    我们一步一步来分析:

    首先要将村庄位置排个序,不用多说。

    下面开始$DP$。

    $No. 1: ext{普通DP}$:

    设$dp[i][j]$表示前$i$个村庄中建了$j$个邮局的最优方案。

    转移方程长这个样:

    $$dp[i][j]=max{ dp[k][j-1]+dis(k+1,i) | kin[0,i)}$$

    其中$dis(l,r)$表示在$[l,r]$这个区间内的村庄中建一个邮局对答案的贡献。

    利用人类智慧可知把邮局建在中间花费最少。。。

    复杂度上限是$O(n^3m)$,实测可以$40 ext{分}$。

    代码如下:

    inline int abs(int x){return x>0?x:-x;}
    inline int dis(int l,int r){
        int mid=(l+r)>>1,s=0;
        for(int i=l;i<=r;i++)s+=abs(pos[i]-pos[mid]);//pos是村庄位置
        return s;
    }
    void work(){
        dp[0][0]=0;
        for(int i=1;i<=n;i++)
        for(int j=1;j<=m&&j<=i;j++)
        for(int k=0;k<i;k++)
        dp[i][j]=min(dp[i][j],dp[k][j-1]+dis(k+1,i));
        printf("%d
    ",dp[n][m]);
    }
    

    $No. 2: ext{普通DP+优化}$:

    我们发现我们的求贡献还要$O(n)$的复杂度,考虑对其进行优化。

    我们知道:

    $$dis(l,r)=sum_{i=l}^r|pos_i-pos_{mid}|,mid=frac{l+r}{2}$$

    因为我们的$pos$是有序的,于是:

    $$dis(l,r)=sum_{i=l}^{mid-1}(pos_{mid}-pos_i)+sum_{i=mid+1}^r(pos_i-pos_{mid}),mid=frac{l+r}{2}$$

    显然拆开:

    $$dis(l,r)=(mid-l)pos_{mid}-sum_{i=l}^{mid-1}pos_i+sum_{i=mid+1}^rpos_i-(r-mid)pos_{mid},mid=frac{l+r}{2}$$

    合并:

    $$dis(l,r)=(2mid-l-r)pos_{mid}+sum_{i=mid+1}^rpos_i-sum_{i=l}^{mid-1}pos_i,mid=frac{l+r}{2}$$

    注意,这个地方$2mid-l-r eq 0$,因为实际算$mid$时,算的其实是$lfloorfrac{l+r}{2} floor$。

    但是这个并不影响复杂度,所以可以保留。

    关键是后面那两个$sum$怎么整?

    前缀和!

    设$sum(x)=sum_{i=1}^xpos_i$,则:

    $$left.egin{array}{}dis(l,r)&=&(2mid-l-r)pos_{mid}+(sum(r)-sum(mid))-(sum(mid-1)-sum(l-1))\&=&(2mid-l-r)pos_{mid}+sum(l-1)+sum(r)-sum(mid)-sum(mid-1)end{array} ight.$$

    于是预处理前缀和,就可以$O(1)$计算$dis(l,r)$了!

    复杂度$O(n^2m)$,实测$60 ext{分}$。

    代码如下:

    inline int dis(int l,int r){
        int mid=(l+r)>>1;
        return ((sum[r]-sum[mid])-(sum[mid-1]-sum[l-1])+(mid*2-l-r)*pos[mid]);
    }
    void work(){
        dp[0][0]=0;
        for(int i=1;i<=n;i++)
        for(int j=1;j<=m&&j<=i;j++)
        for(int k=0;k<i;k++)
        dp[i][j]=min(dp[i][j],dp[k][j-1]+dis(k+1,i));
        printf("%d
    ",dp[n][m]);
    }
    

    $No. 3: ext{DP+四边形不等式}$:

    四边形不等式优化就是:

    在$DP$过程中满足:

    $$dp[a][c]+dp[b][d]<=dp[a][d]+dp[b][c]$$

    并且决策具有单调性。

    对于这题,我们发现,$dp[i][j]$的决策只能从$dp[i][j-1],dp[i+1][j]$中选一个进行转移。

    于是就可以用四边形不等式优化了!

    到此,我们证明了这个题其实是个板子题。。。

    记得要倒序$DP$。

    附代码:

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstring>
    #define MAXN 3010
    using namespace std;
    int n,m;
    int pos[MAXN],sum[MAXN],from[MAXN][MAXN],dp[MAXN][MAXN];
    inline int read(){
    	int date=0,w=1;char c=0;
    	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
    	return date*w;
    }
    inline int dis(int l,int r){
    	int mid=(l+r)>>1;
    	return ((sum[r]-sum[mid])-(sum[mid-1]-sum[l-1])+(mid*2-l-r)*pos[mid]);
    }
    void work(){
    	for(int j=2;j<=m;j++){
    		from[n+1][j]=n;
    		for(int i=n;i>=1;i--){
    			for(int k=from[i][j-1];k<=from[i+1][j];k++){
    				if(dp[k][j-1]+dis(k+1,i)<dp[i][j]){
    					dp[i][j]=dp[k][j-1]+dis(k+1,i);
    					from[i][j]=k;
    				}
    			}
    		}
    	}
    	printf("%d
    ",dp[n][m]);
    }
    void init(){
    	n=read();m=read();
    	memset(dp,127,sizeof(dp));
    	for(int i=1;i<=n;i++)pos[i]=read();
    	sort(pos+1,pos+n+1);
    	sum[0]=0;
    	for(int i=1;i<=n;i++){
    		sum[i]=sum[i-1]+pos[i];
    		dp[i][1]=dis(1,i);
    	}
    }
    int main(){
    	init();
    	work();
        return 0;
    }
    
  • 相关阅读:
    Java 学习笔记(三)调用 bat 或 shell 脚本执行
    Git 学习笔记之(四)在 linux 服务器上 通过 git 命令进行操作
    yum 的使用
    最权威的html 标签属性大全
    webstrom常用快捷键
    CSS样式大全(整理版)
    springboot系列五:springboot整合mybatisplus jsp
    dataX的安装
    where / having / group by / order by / limit 简单查询
    mysql的优化总结
  • 原文地址:https://www.cnblogs.com/Yangrui-Blog/p/9862813.html
Copyright © 2011-2022 走看看