[Luogu P4719] 【模板】动态dp

26 篇文章 0 订阅
20 篇文章 0 订阅
洛谷传送门

题目描述

给定一棵 n n n个点的树,点带点权。

m m m次操作,每次操作给定 x , y x,y x,y,表示修改点 x x x的权值为 y y y

你需要在每次操作之后求出这棵树的最大权独立集的权值大小。

输入输出格式

输入格式:

第一行, n , m n,m n,m,分别代表点数和操作数。

第二行, V 1 , V 2 , . . . , V n V_1,V_2,...,V_n V1,V2,...,Vn,代表 n n n个点的权值。

接下来 n − 1 n-1 n1行, x , y x,y x,y,描述这棵树的 n − 1 n-1 n1条边。

接下来 m m m行, x , y x,y x,y,修改点 x x x的权值为 y y y

对于每个操作输出一行一个整数,代表这次操作后的树上最大权独立集。

保证答案在 i n t int int范围内

输入输出样例

输入样例#1:
10 10
-11 80 -99 -76 56 38 92 -51 -34 47 
2 1
3 1
4 3
5 2
6 2
7 1
8 2
9 4
10 7
9 -44
2 -17
2 98
7 -58
8 48
3 99
8 -61
9 76
9 14
10 93
输出样例#1:
186
186
190
145
189
288
244
320
258
304

说明

对于 30 % 30\% 30%的数据, 1 ≤ n , m ≤ 10 1\le n,m\le 10 1n,m10

对于 60 % 60\% 60%的数据, 1 ≤ n , m ≤ 1000 1\le n,m\le 1000 1n,m1000

对于 100 % 100\% 100%的数据, 1 ≤ n , m ≤ 1 0 5 1\le n,m\le 10^5 1n,m105

解题分析

noip之前瞄了一眼这个玩意, 觉得太毒瘤了就没有细看, 然后居然考了QAQ…

如果不修改的话就是一个 S B 0 / 1 D P SB0/1DP SB0/1DP。 然而如果加上了修改一下就变得毒瘤起来。

这里有一种很神奇的做法:将整颗树先进行树链剖分, 然后对于重链上的每个点, 维护其轻儿子上的信息, 再结合重链向上递推。

具体而言, 设 d p [ i ] [ 1 / 0 ] dp[i][1/0] dp[i][1/0]表示 D F S DFS DFS序为 i i i节点选与不选的子树的最优解, g [ i ] [ 1 / 0 ] g[i][1/0] g[i][1/0]表示选与不选虚儿子的贡献, s o n [ i ] son[i] son[i]表示 i i i的重儿子, 那么就有:
g [ i ] [ 0 ] = ∑ E d g e ( i , j ) , j ≠ s o n [ i ] m a x ( d p [ j ] [ 0 ] , d p [ j ] [ 1 ] ) g [ i ] [ 1 ] = ∑ E d g e ( i , j ) , j ≠ s o n [ i ] d p [ j ] [ 0 ] + v a l [ i ] d p [ i ] [ 0 ] = g [ i ] [ 0 ] + m a x ( d p [ s o n [ i ] ] [ 0 ] , d p [ s o n [ i ] ] [ 1 ] ) d p [ i ] [ 1 ] = g [ i ] [ 1 ] + d p [ s o n [ i ] [ 0 ] ] g[i][0]=\sum_{Edge(i,j),j\ne son[i]} max(dp[j][0],dp[j][1]) \\ g[i][1] =\sum_{Edge(i,j),j\ne son[i]}dp[j][0] +val[i] \\ dp[i][0]=g[i][0]+max(dp[son[i]][0],dp[son[i]][1]) \\ dp[i][1]=g[i][1]+dp[son[i][0]] g[i][0]=Edge(i,j),j̸=son[i]max(dp[j][0],dp[j][1])g[i][1]=Edge(i,j),j̸=son[i]dp[j][0]+val[i]dp[i][0]=g[i][0]+max(dp[son[i]][0],dp[son[i]][1])dp[i][1]=g[i][1]+dp[son[i][0]]
然后我们把矩阵乘法魔改一下, 把原来的乘法换成加法, 把原来的加法换成取 m a x max max, 因为乘法和加法之间满足分配率, 加法满足交换律, 而加法和 m a x max max之间也满足分配率, m a x max max满足交换律, 不难发现它们的关系是等价的, 所以改编出的矩乘也是满足结合律的。

构造出来的矩阵大概是这个样子的:

[ g [ i ] [ 0 ] g [ i ] [ 0 ] g [ i ] [ 1 ] − I N F ] × [ d p [ s o n [ i ] ] [ 0 ] d p [ s o n [ i ] ] [ 1 ] ] = [ d p [ i ] [ 0 ] d p [ i ] [ 1 ] ] \left[ \begin{matrix} g[i][0] & g[i][0]\\ g[i][1] & -INF \end{matrix} \right] \times \left[ \begin{matrix} dp[son[i]][0]\\ dp[son[i]][1] \end{matrix} \right] = \left[ \begin{matrix} dp[i][0]\\ dp[i][1] \end{matrix} \right] [g[i][0]g[i][1]g[i][0]INF]×[dp[son[i]][0]dp[son[i]][1]]=[dp[i][0]dp[i][1]]
然后我们发现似乎对于一个重链末端的点, 第二个矩阵就是单位矩阵, 所以直接维护第一个矩阵的乘积就可以得到一个非末端的点的 d p dp dp值。 换句话说, 对于重链上编号为 i , i + 1 , . . . , j i,i+1,...,j i,i+1,...,j j j j是末端)的一部分, d p [ i ] dp[i] dp[i]可以通过这个后缀连乘积得到。

所以我们就可以用线段树维护区间矩阵乘积, 每次修改的时候单点修改到对应点的 g [ 1 ] g[1] g[1]值, 再查询链顶的 d p dp dp值来更新上一条重链对应的点。 总复杂度 O ( 8 × n l o g 2 ( n ) ) O(8\times nlog^2(n)) O(8×nlog2(n))

代码如下:

#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 100500
#define ll long long
#define INF 1000000000
bool neg;
template <class T>
IN void in(T &x)
{
	x = 0; R char c = gc;
	for (; !isdigit(c); c = gc)
	if (c == '-') neg = true;
	for (;  isdigit(c); c = gc)
	x = (x << 1) + (x << 3) + c - 48;
	if (neg) neg = false, x = -x;
}
template <class T> IN T max(T a, T b) {return a > b ? a : b;}
template <class T> IN T min(T a, T b) {return a < b ? a : b;}
int head[MX], son[MX], siz[MX], fat[MX], top[MX], topf[MX], dfn[MX], end[MX], ind[MX];
ll dp[MX][2], val[MX];
int dot, q, ct, cnt;
struct Edge {int to, nex;} edge[MX << 1];
IN void add(R int from, R int to) {edge[++cnt] = {to, head[from]}, head[from] = cnt;}
struct Matrix
{
	ll mat[2][2];
	Matrix (){mat[0][0] = mat[0][1] = mat[1][0] = mat[1][1] = -INF;}
}tree[MX << 2], bf[MX], unit;
IN Matrix operator + (const Matrix &x, const Matrix &y)
{
	Matrix ret;
	for (R int i = 0; i < 2; ++i)
	for (R int j = 0; j < 2; ++j)
	ret.mat[i][j] = x.mat[i][j] + y.mat[i][j];
	return ret;
}
IN Matrix operator * (const Matrix &x, const Matrix &y)
{
	Matrix ret;
	ret.mat[0][0] = max(x.mat[0][0] + y.mat[0][0], x.mat[0][1] + y.mat[1][0]);
	ret.mat[0][1] = max(x.mat[0][0] + y.mat[0][1], x.mat[0][1] + y.mat[1][1]);
	ret.mat[1][0] = max(x.mat[1][0] + y.mat[0][0], x.mat[1][1] + y.mat[1][0]);
	ret.mat[1][1] = max(x.mat[1][0] + y.mat[0][1], x.mat[1][1] + y.mat[1][1]);
	return ret;
}
void DP(R int now)
{
	dp[now][1] = val[now];
	for (R int i = head[now]; i; i = edge[i].nex)
	{
		if (edge[i].to == fat[now]) continue;
		DP(edge[i].to);
		dp[now][0] += max(dp[edge[i].to][1], dp[edge[i].to][0]);
		dp[now][1] += dp[edge[i].to][0];
	}
}
namespace HLD
{
	#define ls (now << 1)
	#define rs (now << 1 | 1)
	void DFS(R int now)
	{
		siz[now] = 1;
		for (R int i = head[now]; i; i = edge[i].nex)
		{
			if (edge[i].to == fat[now]) continue;
			fat[edge[i].to] = now;
			DFS(edge[i].to); siz[now] += siz[edge[i].to];
			if (siz[edge[i].to] > siz[son[now]]) son[now] = edge[i].to;
		}
	}
	void DFS(R int now, R int grand)
	{
		dfn[now] = ++ct; ind[ct] = now;
		topf[now] = grand;
		if (!son[now]) return end[topf[now]] = dfn[now], void();
		DFS(son[now], grand);
		for (R int i = head[now]; i; i = edge[i].nex)
		{
			if (edge[i].to == fat[now] || edge[i].to == son[now]) continue;
			DFS(edge[i].to, edge[i].to);
		}
	}
	IN void pushup(R int now) {tree[now] = tree[ls] * tree[rs];}
	void build(R int now, R int lef, R int rig)
	{
		if (lef == rig)
		{
			R int cur = ind[lef];
			ll g0 = 0, g1 = val[cur];
			for (R int i = head[cur]; i; i = edge[i].nex)
			{
				if (edge[i].to != son[cur] && edge[i].to != fat[cur])
				{
					g0 += max(dp[edge[i].to][0], dp[edge[i].to][1]);
					g1 += dp[edge[i].to][0];
				}
			}
			tree[now].mat[0][0] = tree[now].mat[0][1] = g0;
			tree[now].mat[1][0] = g1; tree[now].mat[1][1] = -INF;
			bf[lef] = tree[now];
			return;
		}
		int mid = lef + rig >> 1;
		build(ls, lef, mid), build(rs, mid + 1, rig);
		pushup(now);
	}
	void modify(R int now, R int lef, R int rig, R int tar)
	{
		if (lef == rig) return tree[now] = bf[lef], void();
		int mid = lef + rig >> 1;
		if (tar <= mid) modify(ls, lef, mid, tar);
		else modify(rs, mid + 1, rig, tar);
		pushup(now);
	}
	Matrix query(R int now, R int lef, R int rig, R int lb, R int rb)
	{
		if (lef >= lb && rig <= rb) return tree[now];
		int mid = lef + rig >> 1; Matrix ret = unit;
		if (lb <= mid && rb > mid) return query(ls, lef, mid, lb, rb) * query(rs, mid + 1, rig, lb, rb);
		if (lb <= mid) return query(ls, lef, mid, lb, rb);
		return query(rs, mid + 1, rig, lb, rb);
	} 
	Matrix get_tp(R int id) {return query(1, 1, dot, dfn[id], end[id]);}//顶端的dp值
	IN void modify(R int now, ll del)
	{
		bf[dfn[now]].mat[1][0] += del - val[now], val[now] = del;
		Matrix pr, nw;
		W (now)
		{
			pr = get_tp(topf[now]);
			modify(1, 1, dot, dfn[now]);
			nw = get_tp(topf[now]);
			now = fat[topf[now]]; if (!now) break;
			bf[dfn[now]].mat[0][0] += max(nw.mat[0][0], nw.mat[1][0]) - max(pr.mat[0][0], pr.mat[1][0]);
			bf[dfn[now]].mat[0][1] = bf[dfn[now]].mat[0][0];
			bf[dfn[now]].mat[1][0] += nw.mat[0][0] - pr.mat[0][0];
		}
	}
	#undef ls
	#undef rs
}
int main(void)
{
	int a, b, tar; ll buf;
	in(dot), in(q); unit.mat[0][0] = unit.mat[1][1] = 0;
	for (R int i = 1; i <= dot; ++i) in(val[i]);
	for (R int i = 1; i <  dot; ++i) in(a), in(b), add(a, b), add(b, a);
	HLD::DFS(1); HLD::DFS(1, 1); DP(1); HLD::build(1, 1, dot);
	Matrix ret;
	W (q--)
	{
		in(tar), in(buf);
		HLD::modify(tar, buf);
		ret = HLD::get_tp(1);
		printf("%lld\n", max(ret.mat[0][0], ret.mat[1][0]));
	}
}

写出了树剖的版本, 就不难写出 L C T LCT LCT的版本了。 其实大致思想是一样的, 只是 L C T LCT LCT要维护每个点的 g g g值, 用其初始化当前点的矩阵。 a c c e s s access access的时候再向上更新 g g g值就好了。

代码如下:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 100500
#define INF 1000000000
#define ll long long
bool neg;
template <class T>
IN void in(T &x)
{
	x = 0; R char c = gc;
	for (; !isdigit(c); c = gc)
	if (c == '-') neg = true;
	for (;  isdigit(c); c = gc)
	x = (x << 1) + (x << 3) + c - 48;
	if (neg) neg = false, x = -x;
}
template <class T> IN T max(T a, T b) {return a > b ? a : b;}
template <class T> IN T min(T a, T b) {return a < b ? a : b;}

int head[MX], val[MX], fat[MX], sta[MX];
ll dp[MX][2];
int dot, q, cnt, top;
struct Edge {int to, nex;} edge[MX << 1];
IN void add(R int from, R int to) {edge[++cnt] = {to, head[from]}, head[from] = cnt;}
struct Matrix
{
	ll mat[2][2];
	Matrix(){mat[0][0] = mat[0][1] = mat[1][0] = mat[1][1] = -INF;}
	IN void ini(ll a, ll b) {mat[0][0] = mat[0][1] = a, mat[1][0] = b, mat[1][1] = -INF;}
};
IN Matrix operator * (const Matrix &x, const Matrix &y)
{
	Matrix ret;
	ret.mat[0][0] = max(x.mat[0][0] + y.mat[0][0], x.mat[0][1] + y.mat[1][0]);
	ret.mat[0][1] = max(x.mat[0][0] + y.mat[0][1], x.mat[0][1] + y.mat[1][1]);
	ret.mat[1][0] = max(x.mat[1][0] + y.mat[0][0], x.mat[1][1] + y.mat[1][0]);
	ret.mat[1][1] = max(x.mat[1][0] + y.mat[0][1], x.mat[1][1] + y.mat[1][1]);
	return ret;
}
struct Node {int son[2], fat; ll f[2]; Matrix dp; bool rev;} tree[MX];
namespace LCT
{
	#define ls tree[now].son[0]
	#define rs tree[now].son[1]
	#define dad tree[now].fat
	IN bool get(R int now) {return tree[dad].son[1] == now;}
	IN bool nroot(R int now) {return tree[dad].son[0] == now || tree[dad].son[1] == now;}
	IN void pushup(R int now)
	{
		tree[now].dp.ini(tree[now].f[0], tree[now].f[1]);
		if (ls) tree[now].dp = tree[ls].dp * tree[now].dp;
		if (rs) tree[now].dp = tree[now].dp * tree[rs].dp;
	}
	IN void pushrev(R int now) {std::swap(ls, rs), tree[now].rev ^= 1;}
	IN void pushdown(R int now) {if (tree[now].rev) pushrev(ls), pushrev(rs), tree[now].rev = false;}
	IN void rotate(R int now)
	{
		R int fa = dad, grand = tree[fa].fat;
		R bool dir = get(now);
		tree[fa].son[dir] = tree[now].son[dir ^ 1];
		tree[tree[now].son[dir ^ 1]].fat = fa;
		tree[now].fat = grand;
		if (nroot(fa)) tree[grand].son[get(fa)] = now;
		tree[fa].fat = now;
		tree[now].son[dir ^ 1] = fa;
		pushup(fa); pushup(now);
	}
	IN void splay(R int now)
	{
		R int tmp = now, fa;
		sta[top = 1] = now;
		W (nroot(now)) sta[++top] = now = dad;
		W (top) pushdown(sta[top--]);
		now = tmp;
		W (nroot(now))
		{
			fa = dad;
			if (nroot(fa)) rotate(get(now) == get(fa) ? fa : now);
			rotate(now);
		}
		pushup(now);
	}
	IN void access(R int now)
	{
		for (R int x = 0; now; x = now, now = dad)
		{
			splay(now);
			if (rs)
			{
				tree[now].f[0] += max(tree[rs].dp.mat[0][0], tree[rs].dp.mat[1][0]);
				tree[now].f[1] += tree[rs].dp.mat[0][0];
			}
			if (x)
			{
				tree[now].f[0] -= max(tree[x].dp.mat[0][0], tree[x].dp.mat[1][0]);
				tree[now].f[1] -= tree[x].dp.mat[0][0];
			}
			rs = x; pushup(now);
		}
	}
	IN void makeroot(R int now) {access(now), splay(now), pushrev(now);}
	#undef ls
	#undef rs
	#undef dad
}
void DFS(R int now, R int fa)
{
	dp[now][1] = val[now];
	for (R int i = head[now]; i; i = edge[i].nex)
	{
		if (edge[i].to == fa) continue;
		tree[edge[i].to].fat = now;
		DFS(edge[i].to, now);
		dp[now][1] += dp[edge[i].to][0];
		dp[now][0] += max(dp[edge[i].to][0], dp[edge[i].to][1]);
	}
	tree[now].dp.ini(dp[now][0], dp[now][1]);
	tree[now].f[0] = dp[now][0], tree[now].f[1] = dp[now][1];
}
int main(void)
{
	int a, b, tar;
	ll buf;
	in(dot), in(q);
	for (R int i = 1; i <= dot; ++i) in(val[i]);
	for (R int i = 1; i < dot; ++i) in(a), in(b), add(a, b), add(b, a);
	DFS(1, 0);
	W (q--)
	{
		in(tar), in(buf);
		LCT::access(tar), LCT::splay(tar);
		tree[tar].f[1] += buf - val[tar]; val[tar] = buf;
		LCT::pushup(tar); LCT::splay(1);
		printf("%lld\n", max(tree[1].dp.mat[0][0], tree[1].dp.mat[1][0]));
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值