zoukankan      html  css  js  c++  java
  • 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)拆了)

    然后看这个式子找矩阵

    [egin{pmatrix}0&f_vend{pmatrix}$$ $*$ $$egin{pmatrix}0&val_x\infty&g_xend{pmatrix}$$ $=$ $$egin{pmatrix}0&f_xend{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
    ",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做法,对每个节点重儿子开线段树轻儿子开线段树

  • 相关阅读:
    原生开发、H5开发和混合开发的区别?
    html5:FileAPI 文件操作实战
    web前端是编程语言中更新迭代最快的
    HTTP请求过程
    CSS :placeholder-shown伪类实现Material Design占位符交互效果
    Css中bem书写规范
    全栈开发者意味着什么?
    利用canvas实现转盘抽奖
    12种开源Web安全扫描程序
    移动端适配必须掌握的基本概念和适配方案
  • 原文地址:https://www.cnblogs.com/spbv587/p/11626352.html
Copyright © 2011-2022 走看看