【DP+斜率优化】设计铁路

设计铁路   (2011集训队 钱桥)
【题目大意】

A省有一条东西向的公路经常堵车,为解决这一问题,省政府对此展开了调查。调查后得知,这条公路两侧有很多村落,每个村落里都住着很多个信仰c教的教徒,每周日都会开着自家的车沿公路到B地去“膜拜”他们的教主,这便是堵车的原因。详细调查显示:这里总共有N个村落,并且它们都在B地的东边。编号为i的村落住有Ri个信仰c教的教徒,距离B地的距离为Ti(单位:公里)。

为解决这一问题,A省政府决定在这条公路下修建一条地下快速铁路来缓解交通,并沿线修建若干个车站(B地会修建终点站,不算车站)。每名教徒都会先往B地方向开车(如果他所在村庄处恰好有车站就不必开车了),到最近的一个快速铁路车站时换乘(如果直接开到B地就不用换乘了),再通过快速铁路到B地。

但A政府遇到一个难题:修建多少个车站以及在哪修建车站。一个修建车站的方案中,如果修建过多的车站则会花费过多的钱,但修建的车站少了或者修建的位置不对又会导致公路的拥堵。A政府为了协调这两方面,采用评分的方式来衡量一个方案的好坏(分数越大方案越坏):每修建一个车站会增加m的分数,在某一次“膜拜”中(只考虑去,不考虑返回),每导致1个教徒开车行驶1公里会增加1分。

现请你设计一个修建车站的方案,使得分数最小。请输出这个最小的分数。



【题解】

首先要把题目中给出的数据整理一下,按村庄距B地的距离排序,并且有些村庄到B地的距离相等,像这样的村庄可以合并成一个(把教徒数相加)。处理后,有num个村庄,a[i]表示第i个村庄有多少教徒,d[i]表示第i个村庄到B地的距离(排序后对于任意i都有d[i] >d[i+1])。

不难发现动态规划可以解决这一问题:

我们定义F[i]表示第i个村庄处修建一个高速铁路车站,并且1~i号村庄的教徒都开车到车站花费的总分。

F[0]=0;

F[i] = min(F[j] + ∑(d[k]-d[i])*a[k] + m) 其中k取j+1到i;j取0到i-1。

含义为在j处修建车站,j+1到i这些村庄的教徒都开车去i车站,1~j号村庄则转化为了一个子问题。枚举这样的j就能得到最优值。

最后F[num+1]-m为最后所求(因为B处不必因建车站而花费分数)。

这个算法的时间复杂度为O(N^3)。

不难发现,我们在决策时j做一个累加,就不必循环k了,这个简单的小优化可以把时间复杂度降到O(N^2)。

如果我们想进一步优化,必须把转移方程改写一下。

首先我们定义两个类似部分和的数组:

S[i] = ∑d[j]*a[j]    其中j取i+1到n

P[i] = ∑a[j]         其中j取i+1到n

这样对于其中k取j+1到i,∑(d[k]-d[i])*a[k] = s[j] – s[i] –(p[j] – p[i])*d[i]

接下来我们便可以把转移方程改写成:

F[i] = min(F[j] + s[j] – s[i] – (p[j] – p[i])*d[i] + m)

   F[i] = min(F[j] + s[j] – p[j]*d[i]) + m +d[i]*p[i] - s[i]

接着就到重点了,斜率优化。

我们先忽略后面+ m +d[i]*p[i] - s[i]与转移决策无关的量,姑且让f[i] = min(f[j]+s[j]-p[j]*d[i]).

我们把f[i]看作截距b,把f[j]+s[j]一起看作因变量y,把d[i]看作是斜率k,把p[j]看作自变量x。

我们要表示成y=kx+b的形式,(k这个整体的符号要为正,b也是,便于分析)

因为我们要求b最小,所以我们要维护一个下凸壳,点由(x,y)构成(在这题即为(p[j],f[j]+s[j]))

这题中x是关于i递减的,所以我们加点要加在队首(假设队首x坐标小)。

因为斜率d[i]也是关于i递减的,所以我们要维护的决策点在队尾r,保证队尾的两个点的斜率是从尾到头第一个小于当前斜率的,此时f[i]由队尾的f[d[r]]转移是最优的。

若是斜率不单调,我们便要在凸壳上二分,找到一个当斜率恰好切的点。(想想图像)



#include<cstdio>
#include<cstring>
#include<algorithm>
#include<functional>
#define fo(i,a,b) for (int i = a;i <= b;i ++)
#define fd(i,a,b) for (int i = a;i >= b;i --)

using namespace std;
typedef long long LL;
typedef pair<LL,LL> PI;

const int maxn = 40005;
int N,M,l,r,num;
int d[maxn];
LL S[maxn],P[maxn],f[maxn];
PI T[maxn],G[maxn];

double cross(int x,int y)
{
	return (double)((f[y]+S[y])-(f[x]+S[x])) / (P[y]-P[x]);
}

int main()
{
	freopen("design.in","r",stdin);
	freopen("design.out","w",stdout);
	scanf("%d%d",&N,&M);
	fo(i,1,N) scanf("%I64d%I64d",&T[i].first,&T[i].second);
	sort(T+1,T+N+1,greater<PI>());
	fo(i,1,N)
		if (T[i].first == T[i+1].first) T[i+1].second += T[i].second, T[i].second = 0;
	num = 0;
	fo(i,1,N) if (T[i].second) G[++num] = T[i];
	fd(i,num-1,0)
	{
		S[i] = S[i+1] + G[i+1].first * G[i+1].second;
		P[i] = P[i+1] + G[i+1].second;
	}
	int l = r = N+1;
	fo(i,1,num+1)
	{
		while (l < r && cross(d[r-1],d[r]) >= G[i].first) r --;
		f[i] = f[d[r]] + S[d[r]] - P[d[r]]*G[i].first + M + G[i].first*P[i] - S[i];
		d[--l] = i;
		if (i == num+1) break;
		while (l <= r-2 && cross(d[l],d[l+1]) >= cross(d[l+1],d[l+2])) d[l+1] = d[l],l ++;
	}
	printf("%I64d\n",f[num+1]-M);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值