【CEOI2009】harbingers
【题目大意】
给出一棵n个节点树,根为1,给出树上每条边的长度di和这条边连接的两个节点ui,vi。当一个点i需要向根节点传递信息时,需要先花费Si的时间把信息告知节点i的信使,然后信使开始往根节点的方向移动。当信使到的一个新的节点j时,他有两个选择:1,自己继续向根节点前进。2,花Sj的时间把信息交给当地的信使,然后由当地的信使向根节点传递信息。
第i个节点的信使通过一个单位长度的路程所花费的时间Vi。3<=n<=100000,其它出现的数子<=10^9。问从每个节点传递信息到根节点的最短时间。
【题解】
这题动态规划的解法的是显然的。令f[i]为第i个节点传递信息到根节点信息的最短时间。对于i>1,不难列出方程f[i]=min(f[j]+V[i]*(D[i]-D[j])+S[i]),j为i的祖先。
首先考虑条链上的问题。将转移方程拆开,得到f[i]=min(f[j]-V[i]*D[j]))+V[i]*D[i]+S[i]。括号外的部分与j无关,不考虑。对于括号内的部分,将(D[j],f[j])看成平面上的点,用一条斜率为V[i]的直线从x轴下方无穷远处向上移动,碰到的第一个点即为最优解。:: 假设在做f[i]时,(D[j] > D[k]),j比k优,则有(f[j] - f[k]) / (D[j] - D[k]) < V[i].无论V[i]是否单调,不在下凸壳上的点都不可能成为最优的转移点。(画图出来会有收获,用反证法证明)
因此不在下凸壳上的点没有意义。又因为在转移的过程中,当前节点i的D[i]值单调递增,所以可以用一个栈按照从栈底到栈顶斜率严格递增的顺序,来维护下凸壳,每次加入一个点(D[i],f[i])时,首先删除栈顶f[j]>=f[i]的元素,然后删除加入i后导致斜率不递增的栈顶元素,然后将i加入栈中。对于每次查询,可以通过二分查找找到满足和下一个节点的斜率不小于当前节点V[i]的最靠近栈顶的节点,即为最优值。
对于一条链的问题,每个节点最多进栈一次,出栈一次,因此这两种操作可以暴力实现,总的时间复杂度为O(n),每次查询为O(logn),最多n次查询,因此总的时间复杂度为O(nlogn)。
对于一颗树上的问题,同样可以用一条链上的算法。但从子节点返回父亲节点时,需要把被删除的元素重新入栈,这样每个点可能进出栈多次,用暴力实现会TLE。但不难发现,每个节点入栈删除的元素只会是栈顶开始连续的若干个元素,然后入栈。因此如果用一个数组存储单调栈的话,可以用一个元素记录父节点原先栈的大小,令一个元素记录这个节点入栈的位置上原来的元素。这样就可以用O(1)的时间将栈复原。然后对于维护下凸壳的操作,可以用二分查找在O(logn)的时间内计算出要删除的元素棵树。这样维护、查询的复杂度均为O(logn),总的时间复杂度依然为O(nlogn),可以通过本题。
注意:递归会爆栈,要写非递归。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define fo(i,a,b) for (int i = a;i <= b;i ++)
#define x first
#define y second
using namespace std;
typedef long long LL;
const int maxn = 100005;
vector< pair<int,int> > e[maxn];
pair<int,int> a[maxn];
int N,t;
int d[maxn],q[maxn];
LL f[maxn];
struct stack
{
int x,dep,fa,k,_t,_pos,_val;
}S[maxn];
void read(int &x)
{
static char ch;
x = 0;
do ch = getchar(); while (ch < '0' || ch > '9');
do x = x * 10 + ch - 48, ch = getchar(); while (ch >= '0' && ch <= '9');
}
void Initialize()
{
read(N);
fo(i,1,N-1)
{
int x,y,len;
read(x), read(y), read(len);
e[x-1].push_back(make_pair(y-1,len));
e[y-1].push_back(make_pair(x-1,len));
}
fo(i,1,N-1) read(a[i].x),read(a[i].y);
}
inline double cross(int K, int J)
{
return (double) (f[J]-f[K]) / (d[J]-d[K]);
}
void dfs_demo()
{
int C = 1;
S[C].fa = S[C].k = -1;
while (C)
{
int x = S[C].x;
int fa = S[C].fa;
int dep = S[C].dep;
int &k = S[C].k;
int &_pos = S[C]._pos;
int &_val = S[C]._val;
int &_t = S[C]._t;
if (k == -1)
{
d[x] = dep;
f[x] = a[x].x + LL(dep) * a[x].y;
_t = t;
if (t > 1)
{
int p = q[0];
int l,r,mid;
if (cross(q[0],q[1]) < a[x].y)
{
for (l = 0, r = t-2, mid = (l+r+1) >> 1;l < r;mid = (l+r+1)>>1)
if (cross(q[mid],q[mid+1]) < a[x].y)
l = mid;
else r = mid-1;
p = q[l+1];
}
f[x] = min(f[x],f[p]+a[x].x + LL(a[x].y) * (dep-d[p]));
for (l = 1, r = t-1, mid = (l+r+1) >> 1;l < r;mid = (l+r+1)>>1)
if (cross(q[mid-1],q[mid]) < cross(q[mid-1],x))
l = mid;
else r = mid-1;
if (cross(q[l-1],q[l]) > cross(q[l-1],x)) l --;
_pos = l+1, _val = q[_pos];
t = l+2, q[t-1] = x;
}
else
{
_pos = t, _val = q[_pos];
q[t++] = x;
}
k ++;
} else
{
if (k < e[x].size())
{
if (e[x][k].x != fa)
{
S[++C].x = e[x][k].x;
S[C].fa = x;
S[C].dep = dep + e[x][k].y;
S[C].k = -1;
}
k ++;
continue;
} else
{
t = _t;
q[_pos] = _val;
C --;
}
}
}
}
void Work()
{
dfs_demo();
fo(i,1,N-2) printf("%I64d ",f[i]);
printf("%I64d\n",f[N-1]);
}
int main()
{
freopen("harbingers.in","r",stdin);
freopen("harbingers.out","w",stdout);
Initialize();
Work();
return 0;
}