【DP+斜率优化】JZOJ3574 harbingers

【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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值