把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【题解】$test0627 \ $ 数据读取问题 - 树形 $dp$ - 线段树

题目描述

\(\\\)


\(\\\)

\(Solution\)

这题目描述就离谱bushi

考场翻来覆去读了几遍题没看懂kkkkk

大概翻译一下题目意思就是:给你一个序列 \(d\),如果当前读到 \(d_i\) ,就跳到 \(d_i + a_i\) ,目标是最后正好跳到 \(n + 1\) ;对于 \(a_i\) 可以修改为 \(a_i +- y\) ,修改代价为 \(y(y >= 0)\) ;求最小代价。现在有一棵树,对于从根节点到叶子节点的每条链都看做一个序列,求出所有最小代价。

感谢\(\mathtt{I}\)\(\mathtt{\color{red}{makf}}\) 给我讲了好几遍这道题,终于明白力!!!

\(\\\)

考虑只在序列上做

\(dp[i]\) 表示跳到了 \(i\) 的时候的最小代价

若当前 \(dp[i]\) 已知,设 \(j = i + a_i\) ,则 \(dp[j] = min(dp[j], dp[i])\) ,因为可以直接跳到,不需要修改 \(a_i\)

那么来考虑修改,则 $$dp[j - x] = min(dp[j - x], dp[j] + j - x)$$ $$dp[j + x] = min(dp[j + x], dp[j] + x - j)$$

于是可以 \(O(n^2)\) 顺利解决

\(\\\)

接下来考虑优化

先只考虑 \(dp[j] = min(dp[j], dp[i] + i - j)\) 这一种转移(就是上面写的那个式子,改了一下变量),往后推的和这个差不多

想把 \(O(n^2)\) 降为 \(O(n\log n)\)正确做法是在脑子里随便找一种数据结构试试可不可行

直接给结论:开一颗线段树,在第 \(i\) 个叶子节点存的是 \(tree[i] = dp[i] + i\) ,维护最小值

为什么要存这个?其实很巧妙

看看上面的转移方程,如果先不考虑取 \(min\),即 $$dp[j] = dp[i] + i - j$$

得到了 \(dp[j]\) 后我们要存进线段树中,将方程两边加上 \(j\) 就变成了 $$tree[j] = dp[j] + j = dp[i] + i - j + j = dp[i] + i = tree[i]$$

发现直接可以用线段树中的 \(tree[i]\) 更新 \(tree[j]\)

\(\\\)

考虑更新的顺序

由于每个点只能往后跳,所以可以直接按照序列顺序更新

简单证明:若现在已经更新到了 \(i\) ,假设点 \(j > i\) ,若想用 \(j + a_j\) 更新 \(i\) ,相当于把 \(a_j\) 更改为 \(i - j\) ,而 \(a_j\) 不可能是负的,所以无法更新

\(\\\)

考虑在树上做

直接深搜,维护一个线段树,在回溯时撤销操作即可

多写一句关于在树上维护某个数据结构进行撤销操作怎么做

以下所有原数组指的是全局变量的数据结构

\(1.\) 直接开一个数组存下这个原数组,再把原数组进行更新,最后再把这个数组的值赋给原数组

\(2.\) 对于树状数组/线段树,如果是单点修改,那就最多只有 \(\log n\) 个点更改,保存这些点即可

\(3.\) 对于树状数组/线段树,如果是单点修改,可以直接存下在原数组里这个点的值,撤销就用这个值修改一下(比如说在这个点上 \(+3\) ,撤销就在这个点上 \(-3\)

\(\\\)

完结撒花✿✿ヽ(°▽°)ノ✿

\(update(2020.7.2):\) 关于线段树中到了叶子节点直接赋值而不用取 \(min\) ,是因为在修改之前就判断了是否大于当前的值,所以一定要修改。被踩辣 \(\kk\)

\(\\\)


\(\\\)

\(Code\)

#include<bits/stdc++.h>
#define mid (l + r >> 1)
#define ls (k << 1)
#define rs (k << 1| 1)
#define F(i, x, y) for(int i = x; i <= y; ++ i)
using namespace std;
int read();
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
int n, x, y, m;
int a[N], ans[N], dp[N], tree[2][N << 2];
int head[N], cnt, ver[N << 1], nxt[N << 1];
void add(int x, int y){ver[++ cnt] = y, nxt[cnt] = head[x], head[x] = cnt;}
void update(int l, int r, int k, int pos, int v, int op)
{
	if(l == r && l == pos) {tree[op][k] = v; return;}
	if(pos <= mid) update(l, mid, ls, pos, v, op);
	else update(mid + 1, r, rs, pos, v, op);
	tree[op][k] = min(tree[op][ls], tree[op][rs]);
}
int query(int l, int r, int k, int x, int y, int op)
{
	if(l >= x && r <= y) return tree[op][k];
	int res = inf; 
	if(x <= mid) res = min(res, query(l, mid, ls, x, y, op));
	if(y > mid) res = min(res, query(mid + 1, r, rs, x, y, op));
	return res;
}
void dfs(int x, int dep)
{
	int tmp1 = -inf, tmp2 = -inf, u = a[x] + dep, now = dp[dep - 1] + 1;
	if(x != 1)
	{
		int b1 = query(1, n, 1, u, u, 0), b2 = query(1, n, 1, u, u, 1);
		if(b1 > now - u) tmp1 = b1, update(1, n, 1, u, now - u, 0);
		if(b2 > now + u) tmp2 = b2, update(1, n, 1, u, now + u, 1);
		dp[dep] = min(query(1, n, 1, 1, dep, 0) + dep, query(1, n, 1, dep, n, 1) - dep);
	}
	for(int i = head[x]; i; i = nxt[i]) dfs(ver[i], dep + 1);
	if(! head[x]) ans[++ m] = dp[dep];
	if(tmp1 != -inf) update(1, n, 1, u, tmp1, 0);
	if(tmp2 != -inf) update(1, n, 1, u, tmp2, 1);
}
int main()
{
	n = read();
	F(i, 1, n) 
	{
		a[i] = read(), y = read();
		F(j, 1, y) x = read(), add(i, x);
	}
	memset(tree, 0x3f, sizeof(tree));
	dfs(1, 1), sort(ans + 1, ans + m + 1);
	F(i, 1, m) printf("%d\n", ans[i]);
	return 0;
}
int read()
{
	int x = 0, f = 1;
	char c = getchar();
	while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
	while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * f;
}
posted @ 2020-06-28 16:45  Bn_ff  阅读(167)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end