10.5 T3 DDP BZOJ 4712

10.5

T3(bzoj 4712) 毒瘤DDP

题面:

(光看题面就知道有多毒瘤)

​ 就是说让你从一个子树内选一些点, 使得这些点能够把这个子树的所有叶子与子树的根分离开;

​ 首先暴力很好想 , 也很好写, 就是一个树形DP的式子, 每次修改都\(DFS\)维护一遍, \(O(1)\)查询;

\(f_x\)为以\(x\)为根的子树内的答案;

则有\(f_x=min(\sum f_{son}, val_x)\)

贴暴力代码:

void dfs(int x)//暴力 
{
	g[x] = 0;
	if(du[x] == 1&&x != 1) g[x] = 2e9;
	for(edge *i = head[x]; i; i = i->nxt)
	{
		if(i->to == fa[x]) continue;
		fa[i->to] = x;
		dfs(i->to);
	}
	for(edge *i = head[x]; i; i = i->nxt)
	{
		if(i->to == fa[x]) continue;
		g[x] += f[i->to];
	}
	f[x] = min(g[x], w[x]);
}

然后就考虑怎么优化,跑不了要根据这个式子的

考虑把这棵树链剖, 剖完之后边就有了重链和轻边之分,点就有了重儿子 轻儿子之分;

然后刚刚的式子就可变形

\(f(x) = min(g(x)+f(son), val(x))\)

其中\(f\)和上面一样 \(g(x)\)指所有轻儿子的\(f\)值之和,\(f_{son}\)是重儿子(其实就是把刚刚的\(\sum\)拆了)

然后看这个式子找矩阵

\[\begin{pmatrix}0&f_v\end{pmatrix}$$ $*$ $$\begin{pmatrix}0&val_x\\\infty&g_x\end{pmatrix}$$ $=$ $$\begin{pmatrix}0&f_x\end{pmatrix} \]

这里矩阵乘的定义需要改 由以前的相乘之后求和 改为相加之后取\(min\)(因为满足结合律a, 看看式子也知道)

然后就知道\(f_{son}\)(重儿子)通过乘上父亲节点的矩阵就可以转移到\(f_x\)

考虑询问:对每一个节点都维护一个转移矩阵,询问某一个点时直接从叶子乘到那个点就是ta的答案
原因:1 每个点都在重链上
2 每个重链都有叶子节点
3 每个叶子节点转移矩阵是\((0, val_x)=(0 , f_x)\)
然后考虑怎么乘,(当然不是暴力乘啦) 线段树啊, 重链上是连续的DFS序, 树剖基本操作啊
线段树上每个点也放矩阵不就行了吗, 每次找答案的时候就从线段树上扒矩阵。。。。
考虑修改:
显然改一个点的话对ta的子树是没有影响的,只会对父亲及祖先有影响(其实跟这个也没啥关系, 这是二分的做法,找第一个影响的点)
修改的话首先要在线段树上把它矩阵中的\(val\)改了,你会发现
1.对这条链上其他节点的矩阵是没有影响的(因为矩阵里存的\(val\)值和\(g\)值)所以它在本链上只是单点修改;
2. 它会对链顶的父亲的\(g\)产生影响,对链顶父亲也要单点修改;
3. 然后就是重复1.2直到根节点
4.考虑怎么修改链顶的父亲, ta的\(g\)是轻儿子的\(f\)组成的, 现在你链顶的\(f\)已经改了。。
所以要在改之前把链顶\(f\)记录下来, 让其减去旧的链顶\(f\)加上新的链顶\(f\) 即可

好像没了

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 200005
#define int long long
using namespace std;
int read()
{
    register int x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x*f;
}
int n, w[N], m, g[N], fa[N], du[N], f[N];
int rk[N], id[N], size[N], dep[N], son[N], top[N], bot[N], dfn;
struct edge
{
	int to;
	edge *nxt;
	edge(int to, edge *nxt) : to(to), nxt(nxt) {}
} *head[N];
void add(int x, int y)
{
	head[x] = new edge(y, head[x]);
	du[x] ++;
}
void dfs(int x)//暴力 
{
	g[x] = 0;
	if(du[x] == 1&&x != 1) g[x] = 2e9;
	for(edge *i = head[x]; i; i = i->nxt)
	{
		if(i->to == fa[x]) continue;
		fa[i->to] = x;
		dfs(i->to);
	}
	for(edge *i = head[x]; i; i = i->nxt)
	{
		if(i->to == fa[x]) continue;
		g[x] += f[i->to];
	}
	f[x] = min(g[x], w[x]);
}
struct matrix 
{
	int v[3][3];
	friend matrix operator * (const matrix &a, const matrix &b)
	{
		matrix res;
		for(int i = 1; i <= 2; i ++)
		{
			for(int j = 1; j <= 2; j ++)
			{
				res.v[i][j] = 1e17;
				for(int k = 1; k <= 2; k ++)
				{
					res.v[i][j] = min(res.v[i][j], a.v[i][k] + b.v[k][j]);
				}
			}
		}
		return  res;
	}
}a[N];
void dfs1(int x)
{
	size[x] = 1;
	for(edge *i = head[x]; i; i = i->nxt)
	{
		if(i->to == fa[x]) continue;
		fa[i->to] = x;
		dep[i->to] = dep[x] + 1;
		dfs1(i->to);
		size[x] += size[i->to];
		if(size[i->to] > size[son[x]]) son[x] = i->to;
	}
}
void dfs2(int x, int tp)
{
	rk[x] = ++dfn;
	id[dfn] = x; 
	top[x] = tp;
	bot[tp] = x;
	g[x] = 0;
	if(du[x] == 1&&x != 1) g[x] = 2e9;
	if(son[x])  dfs2(son[x], tp);
	for(edge *i = head[x]; i; i = i->nxt)
	{
		if(i->to == fa[x] || i->to == son[x]) continue;
		dfs2(i->to, i->to);
	}
	for(edge *i = head[x]; i ; i = i->nxt)
	{
		if(i->to == fa[x] || i->to == son[x]) continue;
		g[x] += f[i->to];
	}
	f[x] = min(w[x], g[x] + f[son[x]]);
}
struct Segment
{
	struct node
	{
	     int l, r;
	     matrix v;
	     node *li, *ri;
	     node(int l, int r) : l(l), r(r) {
	     	li = NULL, ri = NULL;
	     }
	     int mid() {
	     	return (l + r) >> 1;
	     }
	     void up() {
	     	v = ri->v * li->v;
	    }
	}*root;
	
	void build(node *&k, int l, int r)
	{
		k = new node(l, r);
		if(l == r)
		{
			k->v = a[id[l]];
			return ;
		}
		build(k->li, l, k->mid());
		build(k->ri, k->mid()+1, r);
		k->up();
	}
	void change(node *k , int pos)
	{
		if(k->l == k->r) 
		{
			k->v = a[id[pos]];
			return ;
		}
		if(pos <= k->mid()) change(k->li, pos);
		else change(k->ri, pos);
		k->up();
	}
	matrix ask(node *k, int l, int r)
	{
		if(l == k->l &&r == k->r)
		{
			return k->v;
		}
		if(r <= k->mid())  return ask(k->li, l, r);
		else if(l >= k->mid() + 1) return ask(k->ri, l, r);
		else return ask(k->ri, k->mid()+1, r) * ask(k->li, l, k->mid());
	}	
}A;
int query(int x)
{
	return A.ask(A.root, rk[x], rk[bot[top[x]]]).v[1][2];//某点的f 
}
void update(int x)
{
	while(x) {
		a[x].v[1][2] = w[x];
		a[x].v[2][2] = g[x];//修改节点矩阵 
		A.change(A.root, rk[x]);//维护线段树 
		if(x == 1) break;
		x = top[x];
		g[fa[x]] -= f[x];//维护轻重链交替的地方 
		f[x] = A.ask(A.root, rk[x], rk[bot[top[x]]]).v[1][2];//找到那个地方的f值 
		g[fa[x]] += f[x];
		x = fa[x];//跳到上面的重链 
	}
}
signed main()
{
	freopen("c.in", "r", stdin);
	freopen("c.out", "w", stdout);
	n = read();
	for(int i = 1; i <= n; i ++)
	   w[i] = read();
	for(int i = 1; i < n; i ++)
	{
		int x, y;
		x = read(); y = read();
		add(x, y);  add(y, x);
	}
	fa[1] = 0;
	dep[1] = 1;
	dfs1(1);
	dfs2(1, 1);
	for(int i = 1; i <= n; i ++)//叶子节点矩阵会赋成val 
	{
		a[i].v[1][1] = 0;
		a[i].v[1][2] = w[i];
		a[i].v[2][2] = g[i];
		a[i].v[2][1] = 1e17;
	}
	A.build(A.root, 1, n);
	m = read();
    char s[5];
	for(int i = 1, x, y; i <= m; i ++)
	{
	    scanf("%s", s + 1);
	    if(s[1] == 'Q') 
		{
			x = read();
			printf("%lld\n",query(x));
		}
		else
		{
			x = read(); y = read();
			w[x] += y;
			update(x);//单点修改 更新节点矩阵 
		}
	}	
	return 0;
}
/*
4
4 3 2 1
1 2
1 3
4 2
4
Q 1
Q 2
C 4 10
Q 1
*/

对于大部分ddp 来说

1.找出式子;

2.化成关于重儿子的式子;

3.推出矩阵, 放线段树里

4.修改时跳链;

这是矩阵优化后的DDP可做的;

​ 如果有那种修改没有可减性(就是你不能减去原来的再加上现在的, 比如与, 或运算), 就要用原始的DDP做法,对每个节点重儿子开线段树轻儿子开线段树

posted @ 2019-10-06 07:26  spbv587  阅读(133)  评论(1编辑  收藏  举报