貌似$BZOJ$上并没有这个题。。。
是嫌这个题水了么。。。
还是要氪金权限号???
这里附上洛谷的题面:洛谷P4767 [IOI2000]邮局
高速公路旁边有一些村庄。高速公路表示为整数轴,每个村庄的位置用单个整数坐标标识。没有两个在同样地方的村庄。两个位置之间的距离是其整数坐标差的绝对值。
邮局将建在一些,但不一定是所有的村庄中。为了建立邮局,应选择他们建造的位置,使每个村庄与其最近的邮局之间的距离总和最小。
你要编写一个程序,已知村庄的位置和邮局的数量,计算每个村庄和最近的邮局之间所有距离的最小可能的总和。
输入格式:
第一行包含两个整数:第一个是村庄$V$的数量,第二个是邮局的数量$P$,$1 \leq P \leq 300$,$P \leq V \leq 3000$.
第二行包含$V$个整数。这些整数是村庄的位置。对于每个位置$X$,认为$1 \leq X \leq 10000$。
输出格式:
第一行包含一个整数$S$,它是每个村庄与其最近的邮局之间的所有距离的总和。
对于40%的数据,$V \leq 300$
首先这是个区间$DP$对吧。
我们一步一步来分析:
首先要将村庄位置排个序,不用多说。
下面开始$DP$。
$No. 1:\text{普通DP}$:
设$dp[i][j]$表示前$i$个村庄中建了$j$个邮局的最优方案。
转移方程长这个样:
$$dp[i][j]=\max\{\ dp[k][j-1]+dis(k+1,i)\ |\ k\in[0,i)\}$$
其中$dis(l,r)$表示在$[l,r]$这个区间内的村庄中建一个邮局对答案的贡献。
利用人类智慧可知把邮局建在中间花费最少。。。
复杂度上限是$O(n^3m)$,实测可以$40\text{分}$。
代码如下:
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\n",dp[n][m]); }
$No. 2:\text{普通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 \neq 0$,因为实际算$mid$时,算的其实是$\lfloor\frac{l+r}{2}\rfloor$。
但是这个并不影响复杂度,所以可以保留。
关键是后面那两个$\sum$怎么整?
前缀和!
设$sum(x)=\sum_{i=1}^xpos_i$,则:
$$\left.\begin{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}\right.$$
于是预处理前缀和,就可以$O(1)$计算$dis(l,r)$了!
复杂度$O(n^2m)$,实测$60\text{分}$。
代码如下:
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\n",dp[n][m]); }
$No. 3:\text{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\n",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; }
BZOJXXXX: [IOI2000]邮局——四边形不等式优化初探
原文:https://www.cnblogs.com/Yangrui-Blog/p/9862813.html