【BZOJ 3672】【UOJ #7】【NOI 2014】购票

http://www.lydsy.com/JudgeOnline/problem.php?id=3672

http://uoj.ac/problem/7

链上的情况可以用斜率优化dp。树上用斜率优化dp时,单调队列的复杂度是均摊$O(n)$的,所以放到树上做“可持久化单调队列”复杂度是$O(n^2)$的,所以不能树上斜率优化。

这道题可以用树链剖分(时间复杂度$O(nlog^3n)$)或者点分治套cdq分治(时间复杂度$O(nlog^2n)$)。因为树链剖分感觉比较难写,而且每个节点用vector存单调队列,显得比较卡空间,而且时间复杂度多一个log,所以写了点分治。

对于一个点$i$,从i到根的路径上有$j$,$k$。假设$k$的深度比$j$小,且用$k$来更新$i$比$j$更优,得出式子($dis$为到根的距离):

$$\frac{f_j-f_k}{dis_j-dis_k}>p_i$$

对于每个点,把$dis$看成横坐标,$f$看成纵坐标,最优点一定在下凸壳上。

对于一棵树,求出分治重心,再对重心上方的子树进行分治,分治完后重心到该树的根上所有的点的$f$值都求好了,然后就用重心到根这条链上所有的点去更新重心子树中所有的点。

先对重心子树中所有的点(不包括重心)按“$l$值-到重心的距离”排序,保证用来更新“子树中的点”的“重心到根上的点”到重心的距离单调递增,这样拿单调栈来维护一个下凸壳来更新就可以了。

时间复杂度$O(nlog^2n)$。

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 200003;
int in() {
	int k = 0, fh = 1; char c = getchar();
	for(; c < ‘0‘ || c > ‘9‘; c = getchar())
		if (c == ‘-‘) fh = -1;
	for(; c >= ‘0‘ && c <= ‘9‘; c = getchar())
		k = (k << 3) + (k << 1) + c - ‘0‘;
	return k * fh;
}
ll inll() {
	ll k = 0; int fh = 1; char c = getchar();
	for(; c < ‘0‘ || c > ‘9‘; c = getchar())
		if (c == ‘-‘) fh = -1;
	for(; c >= ‘0‘ && c <= ‘9‘; c = getchar())
		k = (k << 3) + (k << 1) + c - ‘0‘;
	return k * fh;
}

bool vis[N];
struct node {
	int nxt, to; ll w;
	node(int _nxt = 0, int _to = 0, ll _w = 0) : nxt(_nxt), to(_to), w(_w) {}
} E[N << 1];
int t, n, fa[N], Q[N], cnt = 0, sz[N], point[N];
ll f[N], fadis[N], p[N], q[N], l[N], longdis[N];

void ins(int u, int v, ll w) {E[++cnt] = node(point[u], v, w); point[u] = cnt;}

int findrt(int x) {
	int u, head = 0, tail = 1; Q[1] = x;
	while (head != tail) {
		u = Q[++head]; sz[u] = 1;
		for(int i = point[u]; i; i = E[i].nxt)
			if (!vis[E[i].to] && E[i].to != fa[u])
				Q[++tail] = E[i].to;
	}
	for(int i = tail; i >= 1; --i) {
		if ((sz[Q[i]] << 1) > tail) return Q[i];
		sz[fa[Q[i]]] += sz[Q[i]];
	}
}

int tot, qu[N];
struct data {
	ll dis, line; int id;
	data(ll _dis = 0, ll _line = 0, int _id = 0) : dis(_dis), line(_line), id(_id) {}
	bool operator < (const data &A) const {
		return line < A.line;
	}
} a[N];

double k_num(int x, int y) {
	return 1.0 * (f[x] - f[y]) / (longdis[x] - longdis[y]);
}

int find(int le, double k) {
	int left = 0, right = le - 1, mid;
	while (left < right) {
		mid = (left + right) >> 1;
		if (k_num(qu[mid], qu[mid + 1]) > k) left = mid + 1;
		else right = mid;
	}
	if (left == le - 1 && k_num(qu[left], qu[le]) > k) return qu[le];
	return qu[left];
}

int cont = 0;
void cdq(int x) {
	vis[x] = true;
	int i, tmp, tail, head = 0, up = x; ll len = 0;
	for(tmp = point[x]; tmp; tmp = E[tmp].nxt)
		if (!vis[E[tmp].to] && E[tmp].to == fa[x]) {
			while (!vis[fa[up]] && up != 1) up = fa[up];
			cdq(findrt(up));
			break;
		}

	tot = 0;
	for(i = point[x]; i; i = E[i].nxt)
		if (!vis[E[i].to] && E[i].to != fa[x])
			a[++tot] = data(fadis[E[i].to], l[E[i].to] - fadis[E[i].to], E[i].to);

	while (head != tot) {
		++head;
		for(i = point[a[head].id]; i; i = E[i].nxt)
			if (!vis[E[i].to] && E[i].to != fa[a[head].id])
				a[++tot] = data(fadis[E[i].to] + a[head].dis, l[E[i].to] - fadis[E[i].to] - a[head].dis, E[i].to);
	}

	stable_sort(a + 1, a + tot + 1);

	tmp = x;
	while (tmp != up) {
		tmp = fa[tmp]; if (longdis[x] - longdis[tmp] > l[x]) break;
		if (f[x] == -1) f[x] = f[tmp] + (longdis[x] - longdis[tmp]) * p[x] + q[x];
		else f[x] = min(f[x], f[tmp] + (longdis[x] - longdis[tmp]) * p[x] + q[x]);
	}

	tail = 0; tmp = x; qu[0] = x;
	for(i = 1; i <= tot; ++i) {
		if (a[i].line < 0) continue;
		while (tmp != up && len + fadis[tmp] <= a[i].line) {
			len += fadis[tmp];
			tmp = fa[tmp];
			while (tail && k_num(tmp, qu[tail]) > k_num(qu[tail], qu[tail - 1]))
				--tail;
			qu[++tail] = tmp;
		}
		head = find(tail, (double) p[a[i].id]);
		if (f[a[i].id] == -1) f[a[i].id] = f[head] + (longdis[a[i].id] - longdis[head]) * p[a[i].id] + q[a[i].id];
		else f[a[i].id] = min(f[a[i].id], f[head] + (longdis[a[i].id] - longdis[head]) * p[a[i].id] + q[a[i].id]);
	}

	for(i = point[x]; i; i = E[i].nxt)
		if (!vis[E[i].to] && E[i].to != fa[x])
			cdq(findrt(E[i].to));
}

int main() {
	n = in(); t = in();
	for(int i = 2; i <= n; ++i) {
		fa[i] = in(); fadis[i] = inll();
		ins(fa[i], i, fadis[i]);
		ins(i, fa[i], fadis[i]);
		p[i] = inll(); q[i] = inll(); l[i] = inll();
	}

	memset(f, -1, sizeof(ll) * (n + 1));
	f[1] = 0;
	int u, head = 0, tail = 1;
	Q[1] = 1; longdis[1] = 0;
	while (head != tail) {
		u = Q[++head];
		for(int i = point[u]; i; i = E[i].nxt)
			if (E[i].to != fa[u]) {
				longdis[E[i].to] = longdis[u] + fadis[E[i].to];
				Q[++tail] = E[i].to;
			}
	}

	cdq(findrt(1));
	for(int i = 2; i <= n; ++i)
		printf("%lld\n", f[i]);
	return 0;
}

终于调完了,写完后有好多错QAQ

时间: 2024-10-05 04:35:30

【BZOJ 3672】【UOJ #7】【NOI 2014】购票的相关文章

【BZOJ 3669】【NOI 2014】魔法森林 LCT+枚举边

$LCT+枚举$ 复习一下$LCT$模板. 先以$Ai$为关键字$sort$,然后$Ai$从小到大枚举每条边,看能否构成环,构不成则加边,构成则判断,判断过了就切断$Bi$最大的边. 我的边是编号为$i+n$的点,忘了这点调了好久$QAQ$ $sosad$ #include<cstdio> #include<cstring> #include<algorithm> #define N 150003 #define read(x) x=getint() using nam

[BZOJ 3669][NOI 2014]魔法森林(Link-Cut Tree)

题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=3669 记得四个月之前的NOI同步赛,我还只会玩脚丫子.... 记得我当时看到这个题整个人就吓傻了,完全不知道怎么做,然后NOI同步赛就这样爆零了... 如今我学了LCT这个神器,再看这个题,感觉不再那么难了. 其实这个题的标准解法是SPFA,改得完全认不出来的SPFA. orz太神了,完全没见识过这么神犇的SPFA,NOIP 2014考了SPFA,NOI 2014也考了SPFA,

bzoj 3672: [Noi2014]购票 树链剖分+维护凸包

3672: [Noi2014]购票 Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 480  Solved: 212[Submit][Status][Discuss] Description 今年夏天,NOI在SZ市迎来了她30周岁的生日.来自全国 n 个城市的OIer们都会从各地出发,到SZ市参加这次盛会. 全国的城市构成了一棵以SZ市为根的有根树,每个城市与它的父亲用道路连接.为了方便起见,我们将全国的 n 个城市用 1 到 n 的整数编号.其

bzoj 3672 购票 点分治+dp

3672: [Noi2014]购票 Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 1177  Solved: 562[Submit][Status][Discuss] Description 今年夏天,NOI在SZ市迎来了她30周岁的生日.来自全国 n 个城市的OIer们都会从各地出发,到SZ市参加这次盛会. 全国的城市构成了一棵以SZ市为根的有根树,每个城市与它的父亲用道路连接.为了方便起见,我们将全国的 n 个城市用 1 到 n 的整数编号.

●BZOJ 3672 [Noi2014]购票

题链: http://www.lydsy.com/JudgeOnline/problem.php?id=3672 题解: 斜率优化DP,点分治(树上CDQ分治...) 这里有一个没有距离限制的简单版:BZOJ 1767 [Ceoi2009]harbingers 定义$DP[i]$为从i出发到1号点的最小花费,$dis_i$为i到1号点的距离: 转移: $DP[i]=min(DP[j]+(dis_i-dis_j)P_j)+Q_i$ $\quad=min(DP[j]-dis_jP_i)+dis_iP

BZOJ 3672 NOI2014 购票

这题好神啊..好神啊...好神啊... 首先列出N2的DP方程较易. 从DP方程很容易看出来是斜率优化. 如何进一步优化? 考虑对当前点以上的链建立一个下凸包. 维护凸包就可以,但不是很好写. 观察到方程可以必然由它的祖先节点转移.很像Cash那道题. 尝试CDQ分治,每次先递归处理根所在的子树. 然后用根所在的子树,对当前点更新答案,对其他点进行根据dis-lim排序,维护栈即可. 考虑到复杂度,我们需要对树进行点分治. code: #include<iostream> #include&l

bzoj 3816&amp;&amp;uoj #41. [清华集训2014]矩阵变换

稳定婚姻问题: 有n个男生,n个女生,所有女生在每个男生眼里有个排名,反之一样. 将男生和女生两两配对,保证不会出现婚姻不稳定的问题. 即A-1,B-2 而A更喜欢2,2更喜欢A. 算法流程: 每次男生向自己未追求过的排名最高女生求婚. 然后每个有追求者的女生在自己现男友和追求者中选择一个最喜欢的接受,然后拒绝其他人. 算法一定可以结束. 因为如果最后有一个男生单身,那他一定把所有女生都追求过一遍,说明没有女生单身,产生矛盾. #include<iostream> #include<cs

NOI 2014 魔法森林(BZOJ 3669) 题解

对边按a权值排序,按b权值建LCT,按排序后的顺序依次加边.如果加边后形成环则删除环上最大的边.如果起点终点联通则更新答案. 1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,n) for(int i=0;i<n;++i) 4 const int MAXN=50000+5; 5 const int MAXM=100000+5; 6 const int INF=~0U>>1; 7 struct Node

BZOJ 3671 NOI 2014 随机数生成器 贪心

题目大意:实在是太难说明了,自己看pdf吧.. 思路:优先按照它说明的方法处理数组,然后为了让数列中尽可能多的出现小的数字,所以1是必须要出现的,这样才能使整个数列的排序后字典序最小.我们思考,如果2也能在这个数列中那就最好不过了,但是2有可能不在这个数列里,就是2在走了1就不可能走的地方的话,就不能走2了.所以从小到大枚举数字,如果当前数字能走,就输出,然后标记所有走了这个节点就不能走的节点.空间比较紧,5000*5000可以开int*2+bool*1,极限了.. CODE: #include