题意:
给你n个村庄,然后让你用m个邮局对这些村庄进行覆盖,然后让你设计覆盖方式使得每个村庄到其对应邮局的路程和最短
分析:
转移方程:dp[n][m]=min{dp[k][m-1]+W[k+1,n]} k>=m-1&&k<=n-1 dp[i][j]:前i个村庄由j个邮局覆盖
分析一个算法复杂度,状态总数是O(n*m),状态转移需要O(n),那么总的时间复杂度是O(n*n*m)
先来分析下w数组的优化问题,可以假定有6个村庄,村庄的坐标已知分别为p1,p2,p3,p4,p5,p6;那么, 如果要求w[1][4]的话邮局需要建立在2或者3处,放在2处的消耗为p4-p2+p3-p2+p2-p1=p4-p2+p3-p1 放在3处的结果为p4-p3+p3-p2+p3-p1=p4+p3-p2-p1,可见,将邮局建在2处或3处是一样的。现在接着求w[1][5],现在处于中点的村庄是3, 那么1-4到3的距离和刚才已经求出了,即为w[1][4],所以只需再加上5到3的距离即可。 同样,求w[1][6]的时候也可以用w[1][5]加上6到中点的距离。所以有递推关系:w[i][j] = w[i][j-1] + val[j] -val[(i+j)/2]
这题其实可以用四边形不等式做进一步的优化:
转移方程可以变成dp[i][j] = min(dp[k][j-1] + w[k + 1][j]) (s[i-1][j]<=k<=s[i][j+1])
详情请参考 国家集训队2001论文集 毛子青:《动态规划算法的优化技巧》
来自大神的分析:
那么对于邮局这个问题,转移方程是dp[n][m]=min{dp[k][m-1]+W[k+1,n]} k>=m-1&&k<=n-1
很明显与上面的那种形式是类似的,因此我们考虑用四边形不等式进行优化
且由于使用四边形不等式的时候,对于状态dp[i][j] 有个隐含条件是 i<j,因此需要把状态的位置倒一倒
优化成 dp[n][m]=min{dp[k][m-1]+W[k+1,n]}
k=K[n][m]是一个最优间断点,也即一个最优决策点,意思是 前 m-1 个邮局所能覆盖的村庄数
那么有 K[n][m-1]<=k<=K[n+1][m]
注:这里对k这个范围进行限制的时候可能会有一个小疑问,似乎K[n-1][m]<=k<=K[n][m+1]也是满足单调性的
但是很遗憾的是,在这样的状态表示下,这样的区间限制是错的!!!
原因在于要明白这里k具体的含义,k=K[n][m]记录的是在动态规划过程中,对应于状态dp[n][m]的一个最有决策,也即可以说dp[n][m]这个状态是由k这个决策而来,而通过上面的分析,我们可以知道决策具有单调性,因此上面计算出来的一些决策可以拿来对后面的状态转移进行决策区间的限制,那么这里很明显的有 K[n][m-1]<=k<=K[n+1][m] 这个式子成立,意思为 前 m-1 个邮局可以覆盖的村庄数 k,一定要大于等于前 m-2 个邮局可以覆盖的村庄数,所以左边那个不等式成立,而对于右边那个不等式其实只是根据单调性限定的一个上界,我们四边形不等式进行优化的本质其实是在左边那个不等式。其实也可以从动态规划计算的角度去分析,因为对于这样的状态和状态转移方程,我们计算状态的顺序是按照 m 从小到大来的(阶段是m),而四边形不等式优化的本质是通过决策的单调性,我们充分利用以前算出来的决策信息来使得下一阶段枚举决策的范围缩小,那么很明显的是 K[n][m-1]<=K[n][m] 这个不等式是显然必须成立的,是我们优化过程中成功的利用了以前的决策信息的体现。那么如果我们把状态的两个变量的位置调一下,那么 K[n-1][m]<=k<=K[n][m+1] 这样的区间限制就是正确的
小结:k的四边形不等式区间限制,要根据具体k所对应于状态的含义,根据阶段计算的先后性,然后利用决策单调性来进行区间的限制,先是要满足含义上的表示,然后才是决策的单调性,单单满足决策单调性不一定是正确的
//AC CODE:
#include<cstdio>
#include<algorithm>
using namespace std;
#define inf 0x7ffffff
#define maxn 310
int dp[maxn][maxn];//dp[i][j]表示前i个村庄放j个邮局的最短距离
int w[maxn][maxn];//w[i][j]表示[i,j]的最小距离
int val[maxn];
int s[maxn][maxn];//s[i][j]记录 前j- 1个邮局 覆盖 村庄数,即dp[i][j]的最优断开位置
//在j个邮局覆盖时,记录j-1个邮局与j个邮局覆盖时断开的村庄位置
int main()
{
int n,m,i,j;
while(scanf("%d%d",&n,&m)!=EOF)
{
for(i = 1 ; i <= n ; i ++)
scanf("%d",&val[i]);
for(int i = 1 ; i <= n ; i ++)//这里有一个递推公式可以进行预处理
{
w[i][i] = 0;
for(int j = i + 1 ; j <= n ; j ++)
w[i][j] = w[i][j - 1] + val[j] - val[(j + i)/2];
}
//只有一个邮局
for(i = 1 ; i <= n ; i ++)
{
dp[i][1] = w[1][i];//钱i个村庄由1个邮局覆盖
s[i][1] = 0;//1个邮局的前一状态,0个邮局覆盖0个村庄
}
//多个邮局
for(i = 2 ; i <= m ; i ++)//邮局
{
s[n+1][i] = n ;//s[][]上限的初始化 前i-1个邮局覆盖n个村庄
for(j = n ; j > i ; j --)//村庄
{
dp[j][i] = inf;//初始化
/*在 s[j][i-1]到s[j+1][i]的范围内枚举k值{由四边形优化知},
计算前k个村庄建立一个i-1个邮局、第k+1个村庄到第j个村庄建立一个
邮局的距离和.
若该距离为目前最小,则记下方案.*/
//dp[k][i-1] 在第i-2与i-1个邮局之间 ∴s[i][j]记录 前j- 1个邮局 覆盖 村庄数
for(int k = s[j][i-1]; k <= s[j+1][i] ; k ++)
{
int tmp = dp[k][i-1] + w[k + 1][j];
if(tmp < dp[j][i])
{
dp[j][i] = tmp;
s[j][i] = k;
}
}
}
}
//
printf("%d\n",dp[n][m]);
}
}