题目大意:给你一棵 $n$个点 以 $1$为根 的树,每个点有$ 0,1,2 $三种颜色之一,初始时整棵树的颜色均为 $0$。

$m$ 次操作, 每次操作形如:

1 x y c : 将 $x$到$y$的路径上的点全部改为颜色$C$

2 x : 询问 $x$ 所在的同色连通块大小

数据范围:$n,m≤10^5$。

此题一眼动态dp

首先我们先列出正常的dp式子

设$f[u]$表示以$u$为根的子树中,$u$所在的同色联通块大小

显然,$f[u]=1+\sum_{v∈son[u],col[u]=col[v]} f[v]$

若需要输出某个点$x$的答案,我们需要输出$f[y]$,其中$y$是满足$x$至$y$颜色相同的最远祖先。

下面考虑动态dp

我们用树链剖分把原树剖成若干条链,每条链用线段树维护,对于线段树每个节点,我们维护七个变量:

设线段树某个节点表示的区间为$[l_k,r_k]$,这个区间中对应原树的节点编号为$rec[l_k],rec[l_k+1]....rec[r_k]$。

$sum[i]$($0≤i≤2$)表示当前的区间中,颜色为$i$的节点的个数。

$cnt[i]$($0≤i≤2$)表示原树中所有与$rec[l_k....r_k]$相连的,且颜色为$i$的轻儿子中,满足以这些儿子为根的子树中,这些点所在的同色联通快大小之和。

$tag$为涂色标记,表示区间$[l_k,r_k]$中的点是否被刷成了同一个颜色。

首先考虑查询$x$所在的联通块大小,令$id[x]$表示节点x在原树中的$dfs$序,$col[x]$表示第$x$个节点当前的颜色,$top[x]$表示$x$所在重链链顶节点编号

我们首先在$x$所在的重链上,查询出$L$和$R$,满足$L≤x≤R$,且节点$rec[L],rec[L+1]...rec[x]...rec[R]$的颜色是一样的,答案显然要累加上这一段节点的贡献。

查询这部分的贡献我们可以在线段树上向x两侧二分查找即可,详见代码。

然后我们需要加上所有与区间$[L,R]$相连的轻链上的贡献。查询这部分信息直接在线段树上将$cnt$的信息累加一下就可以了。

我们目前只统计了x所在重链的情况,上方的重链我们尚未统计

若$rec[L]=top[x]$,说明$x$所在的联通块可能会与$top[x]$上方的节点相连,这个时候需要去统计下上方的贡献。

否则直接退出就可以了。

下面考虑修改操作

我们按照正常树剖的操作来处理,假设我们当前要更改$[x',x]$的颜色

我们显然现在线段树上对这一个区间更新一下颜色。

然后我们发现,这么操作的话,以$top[x]$为根的树种,$top[x]$所在同色联通块大小可能会变,这个时候我们需要重新求一下“以$top[x]$为根,$top[x]$所在同色联通块大小”,并将这个值上传更新上一条链的cnt值。

(详见代码)

这里有一个要注意的地方,更新区间的颜色可以只更新到$lca(x,y)$,但是更新联通块大小必须更新到根(场上错在这里)

然后就没有了

注意细节!

 #include<bits/stdc++.h>
#define M 100005
#define mid ((a[x].l+a[x].r)>>1)
using namespace std; struct seg{int l,r,tag,sum[],cnt[];}a[M<<]={};
void build(int x,int l,int r){
a[x].l=l; a[x].r=r; a[x].tag=-; if(l==r) return;
build(x<<,l,mid); build(x<<|,mid+,r);
}
void upd(int x,int k){
a[x].sum[]=a[x].sum[]=a[x].sum[]=;
a[x].tag=k; a[x].sum[k]=a[x].r-a[x].l+;
}
void pushdown(int x){
if(a[x].tag!=-) upd(x<<,a[x].tag),upd(x<<|,a[x].tag);
a[x].tag=-;
}
void pushup(int x){
for(int i=;i<;i++){
a[x].sum[i]=a[x<<].sum[i]+a[x<<|].sum[i];
a[x].cnt[i]=a[x<<].cnt[i]+a[x<<|].cnt[i];
}
}
void updatacol(int x,int l,int r,int col){
if(l<=a[x].l&&a[x].r<=r) return upd(x,col);
pushdown(x);
if(l<=mid) updatacol(x<<,l,r,col);
if(mid<r) updatacol(x<<|,l,r,col);
pushup(x);
}
void updatacnt(int x,int id,int col,int val){
if(a[x].l==a[x].r)return void(a[x].cnt[col]+=val);
pushdown(x);
if(id<=mid) updatacnt(x<<,id,col,val);
else updatacnt(x<<|,id,col,val);
pushup(x);
}
int querycnt(int x,int l,int r,int col){
if(l<=a[x].l&&a[x].r<=r) return a[x].cnt[col];
pushdown(x); int res=;
if(l<=mid) res+=querycnt(x<<,l,r,col);
if(mid<r) res+=querycnt(x<<|,l,r,col);
return res;
}
int querycol(int x,int id){
if(a[x].l==a[x].r){
if(a[x].sum[]) return ;
if(a[x].sum[]) return ;
return ;
}
pushdown(x);
if(id<=mid) return querycol(x<<,id);
return querycol(x<<|,id);
}
int queryUP(int x,int l,int r,int col){
if(l<=a[x].l&&a[x].r<=r){
if(a[x].sum[col]==a[x].r-a[x].l+) return a[x].l;
}
if(a[x].l==a[x].r) return ;
pushdown(x);
if(mid<r){
int res=queryUP(x<<|,l,r,col);
if(res!=mid+) return res;
int res2=;
if(l<=mid) res2=queryUP(x<<,l,r,col);
if(res2) return res2;
return res;
}
return queryUP(x<<,l,r,col);
}
int queryDN(int x,int l,int r,int col){
if(l<=a[x].l&&a[x].r<=r){
if(a[x].sum[col]==a[x].r-a[x].l+) return a[x].r;
}
if(a[x].l==a[x].r) return ;
pushdown(x);
if(l<=mid){
int res=queryDN(x<<,l,r,col);
if(res!=mid) return res;
int res2=;
if(mid<r) res2=queryDN(x<<|,l,r,col);
if(res2) return res2;
return res;
}
return queryDN(x<<|,l,r,col);
} struct edge{int u,next;}e[M]={}; int head[M]={},use=;
void add(int x,int y){use++;e[use].u=y;e[use].next=head[x];head[x]=use;}
int fa[M]={},id[M]={},siz[M]={},son[M]={},dep[M]={},top[M]={},dn[M]={},t=,n,m;
int last[M]={},rec[M]={}; void dfs(int x){
siz[x]=;
for(int i=head[x];i;i=e[i].next){
fa[e[i].u]=x; dep[e[i].u]=dep[x]+;
dfs(e[i].u);
siz[x]+=siz[e[i].u];
if(siz[son[x]]<siz[e[i].u]) son[x]=e[i].u;
}
}
void dfs(int x,int Top){
top[x]=Top; id[x]=++t; rec[t]=x;
if(son[x]){
dfs(son[x],Top);
dn[x]=dn[son[x]];
}else{
updatacol(,id[Top],id[x],);
dn[x]=x;
}
for(int i=head[x];i;i=e[i].next) if(e[i].u!=son[x]){
dfs(e[i].u,e[i].u);
updatacnt(,id[x],,last[e[i].u]=siz[e[i].u]);
}
} int Query(int x,int col){
if(!x) return ;
int l=id[top[x]],r=id[dn[x]];
int L=queryUP(,l,id[x],col);
int R=queryDN(,id[x],r,col);
int res=(R-L+)+querycnt(,L,R,col);
if(rec[L]==top[x]&&fa[top[x]]&&querycol(,id[fa[top[x]]])==col)
return Query(fa[top[x]],col);
return res;
} void Updata(int x,int y,int col,int chg){
if(!x) return;
int Lastcol;
if(top[x]==top[y]){
if(dep[x]<dep[y]) swap(x,y);
Lastcol=querycol(,id[top[x]]);
if(chg) updatacol(,id[y],id[x],col);
}else{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
Lastcol=querycol(,id[top[x]]);
if(chg) updatacol(,id[top[x]],id[x],col);
}
int Topcol=querycol(,id[top[x]]);
int L=id[top[x]];
int R=queryDN(,L,id[dn[x]],Topcol);
int val=(R-L+)+querycnt(,L,R,Topcol);
if(fa[top[x]]){
updatacnt(,id[fa[top[x]]],Lastcol,-last[top[x]]);
updatacnt(,id[fa[top[x]]],Topcol,val);
}
last[top[x]]=val;
if(top[x]!=top[y]) Updata(fa[top[x]],y,col,);
else Updata(fa[top[x]],fa[top[x]],col,);
} int main(){
scanf("%d%d",&n,&m);
build(,,n);
for(int i=,x;i<=n;i++) scanf("%d",&x),add(x,i);
dfs(); dfs(,);
while(m--){
int op,x,y,col; scanf("%d%d",&op,&x);
if(op==) printf("%d\n",Query(x,querycol(,id[x])));
else{
scanf("%d%d",&y,&col);
Updata(x,y,col,);
}
}
}

【xsy2913】 enos 动态dp的更多相关文章

  1. 动态DP之全局平衡二叉树

    目录 前置知识 全局平衡二叉树 大致介绍 建图过程 修改过程 询问过程 时间复杂度的证明 板题 前置知识 在学习如何使用全局平衡二叉树之前,你首先要知道如何使用树链剖分解决动态DP问题.这里仅做一个简 ...

  2. Luogu P4643 【模板】动态dp

    题目链接 Luogu P4643 题解 猫锟在WC2018讲的黑科技--动态DP,就是一个画风正常的DP问题再加上一个动态修改操作,就像这道题一样.(这道题也是PPT中的例题) 动态DP的一个套路是把 ...

  3. 动态dp学习笔记

    我们经常会遇到一些问题,是一些dp的模型,但是加上了什么待修改强制在线之类的,十分毒瘤,如果能有一个模式化的东西解决这类问题就会非常好. 给定一棵n个点的树,点带点权. 有m次操作,每次操作给定x,y ...

  4. 洛谷P4719 动态dp

    动态DP其实挺简单一个东西. 把DP值的定义改成去掉重儿子之后的DP值. 重链上的答案就用线段树/lct维护,维护子段/矩阵都可以.其实本质上差不多... 修改的时候在log个线段树上修改.轻儿子所在 ...

  5. 动态 DP 学习笔记

    不得不承认,去年提高组 D2T3 对动态 DP 起到了良好的普及效果. 动态 DP 主要用于解决一类问题.这类问题一般原本都是较为简单的树上 DP 问题,但是被套上了丧心病狂的修改点权的操作.举个例子 ...

  6. 动态dp初探

    动态dp初探 动态区间最大子段和问题 给出长度为\(n\)的序列和\(m\)次操作,每次修改一个元素的值或查询区间的最大字段和(SP1714 GSS3). 设\(f[i]\)为以下标\(i\)结尾的最 ...

  7. [总结] 动态DP学习笔记

    学习了一下动态DP 问题的来源: 给定一棵 \(n\) 个节点的树,点有点权,有 \(m\) 次修改单点点权的操作,回答每次操作之后的最大带权独立集大小. 首先一个显然的 \(O(nm)\) 的做法就 ...

  8. UOJ268 [清华集训2016] 数据交互 【动态DP】【堆】【树链剖分】【线段树】

    题目分析: 不难发现可以用动态DP做. 题目相当于是要我求一条路径,所有与路径有交的链的代价加入进去,要求代价最大. 我们把链的代价分成两个部分:一部分将代价加入$LCA$之中,用$g$数组保存:另一 ...

  9. [复习]动态dp

    [复习]动态dp 你还是可以认为我原来写的动态dp就是在扯蛋. [Luogu4719][模板]动态dp 首先作为一个\(dp\)题,我们显然可以每次修改之后都进行暴力\(dp\),设\(f[i][0/ ...

随机推荐

  1. Android 从相机或相册或获取图片(转)

    参考: https://github.com/ASDbobo/GetPhotoDemo Android 8.0 调取系统摄像头和相册选择图片 9.3 使用Camera拍照

  2. #pragma warning(disable 4786)

    #pragma warning(disable 4786) 此warning产生的原因是因为标识符过长,超过了最大限定255个字符类名超过了255个字符,使用时就会报4786的waring. 在使用S ...

  3. understand试用笔记一阅读VS2010项目

    一.查看vs2010项目 打开understand,File—New—Project...—Next—Next [向导第三步,选“Import Visual Sudio project files”] ...

  4. python 基础_ 打印输出 循环分支2

    一.在python3中的打印输出 1.输出字符串是print("hello world!!!") #输出字符串的时候可以是单引号括起来,也可以是双引号括起来.区别在于 2.输出变量 ...

  5. head内部标签(常用部分)

    1.meta标签: <meta charset="utf-8" /> 2 <meta name="keywords" content=&quo ...

  6. 百度Web Uploader组件实现文件上传之分片上传(一)

    当网络问题导致传输错误时,只需要重传出错分片,而不是整个文件.另外分片传输能够更加实时的跟踪上传进度.多的不说了直接怼代码 前端是三个监听:一个是获取md5,一个是分片,最后一个是合并代码 <! ...

  7. 解决UITableView上的cell的重用

    1.通过为每个cell指定不同的重用标识符(reuseIdentifier)来解决 //        static NSString *rankCellIndefier = @"rankC ...

  8. Write Markdown Syntax Online Document with Sphinx and Pandoc

    There is no doubt that we have to write doc while we are developing software. But How do you write d ...

  9. leetcode - [6]Binary Tree Postorder Traversal

    Given a binary tree, return the postorder traversal of its nodes' values. For example:Given binary t ...

  10. Java Applet小应用

    开发和部署方式     嵌入到HTML网页中,用<Applet></Applet>标签识别.java环境用浏览器的,在第一次打开时下载,可开发成以后打开,默认不必再次下载.也可 ...