標題:居民集會
藍橋村的居民都生活在一條公路的邊上,公路的長度爲L,每戶家庭的位置都用這戶家庭到公路的起點的距離來計算,第i戶家庭距起點的距離爲di。
每年,藍橋村都要舉行一次集會。今年,由於村裏的人口太多,村委會決定要在4個地方舉行集會,其中3個位於公路中間,1個位最公路的終點。
已知每戶家庭都會向着遠離公路起點的方向去參加集會,參加集會的路程開銷爲家庭內的人數ti與距離的乘積。
給定每戶家庭的位置di和人數ti,請爲村委會尋找最好的集會舉辦地:p1, p2, p3, p4 (p1<=p2<=p3<=p4=L),使得村內所有人的路程開銷和最小。
【輸入格式】
輸入的第一行包含兩個整數n, L,分別表示藍橋村的家庭數和公路長度。
接下來n行,每行兩個整數di, ti,分別表示第i戶家庭距離公路起點的距離和家庭中的人數。
【輸出格式】
輸出一行,包含一個整數,表示村內所有人路程的開銷和。
【樣例輸入】
6 10
1 3
2 2
4 5
5 20
6 5
8 7
【樣例輸出】
18
【樣例說明】
在距起點2, 5, 8, 10這4個地方集會,6個家庭需要的走的距離分別爲1, 0, 1, 0, 2, 0,總的路程開銷爲1*3+0*2+1*5+0*20+2*5+0*7=18。
【數據規模與約定】
對於10%的評測數據,1<=n<=300。
對於30%的評測數據,1<=n<=2000,1<=L<=10000,0<=di<=L,di<=di+1,0<=ti<=20。
對於100%的評測數據,1<=n<=100000,1<=L<=1000000,0<=di<=L,di<=di+1,0<=ti<=1000000。
要在一條線上找到4個點(3箇中間點加1個端點)來使得其他點到這些點的距離與權值乘積最小。
使用枚舉的方法可以枚舉3箇中間點,這樣的複雜度爲O(n^4),使用前綴和可以優化到O(n^3)。。
使用動態規劃,令f[i][j]表示在前i個地方建j個端點的最小答案。
則f[i][j]=f[k][j-1]+cost(i+1,j)。這種方法需要枚舉k,複雜度爲O(n^2)
要想獲得更多的分,需要對動態規劃進行優化。
這裏考慮i是第二個選擇的點的時候它的前一個點j的取值,通過列出公式可知j的選擇相對於i的遞增是遞增的,而且滿足斜率優化的性質,可以維護一個下凸序列通過單調隊列來優化,在O(n)的時間內求出第二個選擇的點是i時前面一斷的最優值f[i]。
通過類似的方法可以求出當第二個選擇的點是i時後面一斷的最優值g[i],這裏需要推導不同的公式,得到不同的斜率優化的結果,但基本思路和推導過程相似。將f[i]+g[i]取個最小值即可得到答案。
這種方法推導具有較高難度,在實現過程中,由於數據範圍特別大,雖然答案在long long範圍內,但對於斜率優化的判斷中,即使使用long long也會出現越界情況,需要考慮到一點,使用double或者高精度計算來解決越界問題。
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 100100;
int n, L;
long long d[MAXN], t[MAXN];
long long s[MAXN], sd[MAXN], f[MAXN], g[MAXN];
int sta[MAXN], top;
long long ans;
void init()
{
scanf("%d%d", &n, &L);
for (int i = 1; i <= n; ++i)
{
int x, y;
scanf("%d%d", &x, &y);
d[i] = x;
t[i] = y;
}
++n;
d[n] = L;
t[n] = 0;
int j = 1;
for (int i = 2; i <= n; ++i)
{
if (d[i]==d[j])
t[j] += t[i];
else
{
++j;
d[j] = d[i];
t[j] = t[i];
}
}
n = j;
}
class BigNumber {
public:
int l, sig;
int s[100];
BigNumber(long long x)
{
if (x < 0)
{
sig = -1;
x = -x;
}
else
sig = 1;
memset(s, 0, sizeof(s));
l = 0;
while (x>0LL)
{
s[l++] = x%10;
x /= 10;
}
if (l==0)
l = 1;
}
BigNumber operator *(const BigNumber &r) const {
BigNumber ret(0LL);
ret.sig = sig * r.sig;
ret.l = l + r.l - 1;
for (int i = 0; i < l; ++i)
for (int j = 0; j < r.l; ++j)
ret.s[i+j] += s[i] * r.s[j];
for (int i = 0; i < ret.l; ++i)
{
ret.s[i+1] += ret.s[i] / 10;
ret.s[i] %= 10;
}
if (ret.s[ret.l])
++ret.l;
while (ret.l>1 && ret.s[ret.l-1]==0)
--ret.l;
if (ret.l==1 && ret.s[0]==0)
ret.sig = 1;
return ret;
}
bool operator <(const BigNumber &r) const {
if (sig != r.sig) return sig < r.sig;
if (l != r.l) return (l < r.l) ^ (sig == -1);
for (int i = l - 1; i >= 0; --i)
if (s[i] != r.s[i])
return (s[i] < r.s[i]) ^ (sig == -1);
return false;
}
};
void process()
{
long long oo = 100000LL * 1000000LL * 1000000LL + 1;
s[0] = 0;
sd[0] = 0;
for (int i = 1; i <= n; ++i)
{
s[i] = s[i-1] + t[i];
sd[i] = s[i] * d[i];
}
sta[0] = 0;
top = 1;
int best = 0;
for (int i = 1; i <= n; ++i)
{
while (best+1<top && d[i]*(s[sta[best+1]]-s[sta[best]])>=(sd[sta[best+1]]-sd[sta[best]]))
++best;
f[i] = s[i] * d[i] - s[sta[best]] * (d[i] - d[sta[best]]);
while (top>1 && BigNumber(sd[i]-sd[sta[top-1]])*BigNumber(s[sta[top-1]]-s[sta[top-2]])<BigNumber(sd[sta[top-1]]-sd[sta[top-2]])*BigNumber(s[i]-s[sta[top-1]]))
--top;
sta[top++] = i;
if (best>=top)
best = top-1;
}
for (int i = 1; i <= n; ++i)
sd[i] = s[i] * (d[n]-d[i]);
sta[0] = n;
g[n] = 0;
top = 1;
best = 0;
for (int i = n - 1; i >= 1; --i)
{
while (best+1<top && s[i]*(d[sta[best]]-d[sta[best+1]])<=-(sd[sta[best]]-sd[sta[best+1]]))
{
++best;
}
g[i] = (s[sta[best]]-s[i]) * d[sta[best]] + (s[n]-s[sta[best]]) * d[n];
while (top>1 && BigNumber(sd[i]-sd[sta[top-1]])*BigNumber(d[sta[top-1]]-d[sta[top-2]])<BigNumber(sd[sta[top-1]]-sd[sta[top-2]])*BigNumber(d[i]-d[sta[top-1]]))
--top;
sta[top++] = i;
if (best>=top)
best = top-1;
}
ans = oo;
for (int i = 1; i <= n; ++i)
if (f[i] + g[i] < ans)
ans = f[i] + g[i];
long long tmp = 0;
for (int i = 1; i <= n; ++i)
ans -= (t[i]+0LL) * d[i];
}
void print()
{
cout << ans << endl;
}
int main()
{
init();
process();
print();
return 0;
}