题目大意:给你一棵 $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. MySQL mysqldump 数据备份

    1.mysqldump 命令工具说明 参数注解: mysqldump 是采用SQL 级别的备份机制,它将数据表导成 SQL 脚本文件,在不同的 MySQL 版本之间升级时相对比较合适,这也是最常用的备 ...

  2. 2018.10.25 atcoder Leftmost Ball(计数dp+组合数学)

    传送门 dp妙题啊. 我认为DZYODZYODZYO已经说的很好了. 强制规定球的排序方式. 然后就变成了一个求拓扑序数量的问题. 代码: #include<bits/stdc++.h> ...

  3. JAVA遇上HTML-----JSP 篇基本概念

    Java Web简介 1.什么是WEB应用程序: Web应用程序是一种可以通过Web访问的应用程序.Web应用程序的一个最大好处是用户很容易访问应用程序.用户只需要有浏览器即可,不需要再安装其他软件. ...

  4. vue 开发系列(六) 企业微信整合

    概述 手机端程序可以和企业微信进行整合,我们也可以使用企业微信JSSDK功能,实现一些原生的功能. 整合步骤 在整合之前需要阅读 整合步骤. http://work.weixin.qq.com/api ...

  5. 第13章:MongoDB-聚合操作--初体验

    ①MongoDB 的聚合功能 MongoDB 的聚合功能,聚合操作主要用于对数据的批量处理,往往将记录按条件分组以后,然后再进行一系列操作,例如,求最大值.最小值.平均值,求和等操作. 聚合操作还能够 ...

  6. Silverlight中图片显示

    silverlight中显示一个图片有很多的中方法,xaml中的image控件或者自定编写程序来生成image控件. silverlight中显示的图片只能是Bitmap, JPG, PNG(64位颜 ...

  7. Redis集群的主从切换研究

    目录 目录 1 1. 前言 1 2. slave发起选举 2 3. master响应选举 5 4. 选举示例 5 5. 哈希槽传播方式 6 6. 一次主从切换记录1 6 6.1. 相关参数 6 6.2 ...

  8. C++获取当前进程绝对路径

    获取进程的绝对路径(代码同时操作字符串获取了文件目录): 第一种代码: wstring GetProgramDir() { TCHAR exeFullPath[MAX_PATH]; // Full p ...

  9. 基于udp协议的套接字,socketserver模块,多道技术,进程理论

    进程指的是一个正在进行/运行的程序,进程是用来描述程序执行过程的虚拟概念 进程vs程序 程序:一堆的代码 进程:程序执行的过程 进程的概念起源于操作系统,进程是操作系统最核心的概念,操作系统的其他所有 ...

  10. Hdu1151 Air Raid(最小覆盖路径)

    Air Raid Problem Description Consider a town where all the streets are one-way and each street leads ...