bzoj4034 (树链剖分)

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4034

                                       4034: [HAOI2015]树上操作

                                                         Time Limit: 10 Sec  Memory Limit: 256 MB
                                                                    Submit: 7576  Solved: 2597
                                                                      [Submit][Status][Discuss]

Description

有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个

操作,分为三种:

操作 1 :把某个节点 x 的点权增加 a 。

操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。

操作 3 :询问某个节点 x 到根的路径中所有点的点权和。

Input

第一行包含两个整数 N, M 。表示点数和操作数。接下来一行 N 个整数,表示树中节点的初始权值。接下来 N-1 

行每行三个正整数 fr, to , 表示该树中存在一条边 (fr, to) 。再接下来 M 行,每行分别表示一次操作。其中

第一个数表示该操作的种类( 1-3 ) ,之后接这个操作的参数( x 或者 x a ) 。

Output

对于每个询问操作,输出该询问的答案。答案之间用换行隔开。

Sample Input

5 5
1 2 3 4 5
1 2
1 4
2 3
2 5
3 3
1 2 1
3 5
2 1 2
3 3

Sample Output

6
9
13

 

思路:好吧,其实这道题并不是难题,而是一道树链剖分的入门题。由于我太弱了,这道题仍然给我带来了一定的困扰,卡了我整整一个下午。最后发现有一个地方少判断了一种情况,QAQ......

首先修改点权很常规,板子一套就好了。

修改子树时,需找到当前点的dfs序的范围,也就是在刚dfs到这个点时,记录一下是第几个,等dfs完它的所有儿子时,mx[u]=max(mx[u],mx[son]),也就是将它的最大范围更新为其所有儿子能达到的最大范围。然后用线段树区间更新就好了。

查询时就是裸的线段树区间查询,自己再搞一搞就好了。。。

AC代码(又写了200多行)

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
using namespace std;
typedef long long LL;
typedef long long ll;
const int MAXN = 100010;
struct Edge
{
    int to, next;
} edge[MAXN<<2];

int n;
int a[MAXN];
int head[MAXN], tot;
int top[MAXN];  
int fa[MAXN];   
int deep[MAXN]; 
int num[MAXN];  
int p[MAXN];    
int fp[MAXN];   
int son[MAXN];  
int pos;
int mx[MAXN];

void init()
{
    tot = 0;
    memset(head, -1, sizeof(head));
    pos = 0;    
    memset(son, -1, sizeof(son));
    return ;
}

void addedge(int u, int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
    return ;
}

void dfs1(int u, int pre, int d)
{
    deep[u] = d;
    fa[u] = pre;
    num[u] = 1;
    for (int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        if (v != pre)
        {
            dfs1(v, u, d + 1);
            num[u] += num[v];
            if (son[u] == -1 || num[v] > num[son[u]])
            {
                son[u] = v;
            }
        }
    }
    return ;
}

void dfs2(int u, int sp)
{
    top[u] = sp;
    ++pos;
    p[u] = mx[u] = pos;
    fp[p[u]] = u;
    if (son[u] == -1)
    {
        return ;
    }
    dfs2(son[u], sp);
    mx[u]=max(mx[u],mx[son[u]]);
    for (int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        if (v != son[u] && v != fa[u])
        {
            dfs2(v, v);
            mx[u]=max(mx[u],mx[v]);
        }
    }
    return ;
}

LL add[MAXN<<2];
LL sum[MAXN<<2];

void PushUp(int rt) {
       sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
void PushDown(int rt,int m) {
       if (add[rt]) {
              add[rt<<1] += add[rt];
              add[rt<<1|1] += add[rt];
              sum[rt<<1] += add[rt] * (m - (m >> 1));
              sum[rt<<1|1] += add[rt] * (m >> 1);
              add[rt] = 0;
       }
}
void build(int l,int r,int rt) {
       add[rt] = 0;
       if (l == r) 
	   {
            sum[rt]=a[fp[l]];
            return ;
       }
       int m = (l + r) >> 1;
       build(lson);
       build(rson);
       PushUp(rt);
}
void update(int L,int R,ll c,int l,int r,int rt) {
       if (L <= l && r <= R) {
              add[rt] += c;
              sum[rt] += (LL)c * (r - l + 1);
              return ;
       }
       PushDown(rt , r - l + 1);
       int m = (l + r) >> 1;
       if (L <= m) update(L , R , c , lson);
       if (m < R) update(L , R , c , rson);
       PushUp(rt);
}
LL query(int L,int R,int l,int r,int rt) {
       if (L <= l && r <= R) {
              return sum[rt];
       }
       PushDown(rt , r - l + 1);
       int m = (l + r) >> 1;
       LL ret = 0;
       if (L <= m) ret += query(L , R , lson);
       if (m < R) ret += query(L , R , rson);
       return ret;
}

ll query_path(int x, int y) 
{
    ll ans = 0;
    int fx = top[x], fy = top[y];
    while (fx != fy) {
        if (deep[fx] >= deep[fy]) {
            ans += query(p[fx],p[x],1,n,1);
            x = fa[fx];
        } 
		else {
            ans += query(p[fy],p[y],1,n,1);
            y = fa[fy];
        }
        fx = top[x], fy = top[y];
    }

    if (x != y) 
	{
        if (p[x] < p[y]) 
		{
            ans += query(p[x],p[y],1,n,1);
        } 
		else 
		{
            ans += query(p[y],p[x],1,n,1);
        }
    } 
	else ans += query(p[x],p[y],1,n,1);
    return ans;
}

int main()
{
	  int m;
	  int u,v,op,x;
	  ll tem;
	  scanf("%d%d",&n,&m);
	  init();
	  for(int i=1;i<=n;i++)
	  scanf("%d",&a[i]);
	  for(int i=1;i<n;i++)
	  {
	  	   scanf("%d%d",&u,&v);
	  	   addedge(u,v);
	  	   addedge(v,u);
	  }
	  dfs1(1, 0, 0);
	  dfs2(1,1);
	  build(1,n,1);
	  while(m--)
	  {
	  	   scanf("%d",&op);
	  	   if(op==1)
	  	   {
	  	        scanf("%d%lld",&x,&tem);
				update(p[x],p[x],tem,1,n,1);	   
		   }
		   else if(op==2)
		   {
		   	    scanf("%d%lld",&x,&tem);
				update(p[x],mx[x],tem,1,n,1);
		   }
		   else if(op==3)
		   {
		   	    scanf("%d",&x);
				printf("%lld\n",query_path(1,x));
		   }	   
	   }
	   return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值