题目大意:给你一棵 $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. 2018.12.18 bzoj5296: [Cqoi2018]破解D-H协议(bsgs)

    传送门 bsgsbsgsbsgs基础题. 考虑到给的是原根,因此没无解的情况. 于是只需要每次把a,ba,ba,b解出来. 然后可以通过预处理节省一部分时间. 代码: #include<bits ...

  2. java常量

    一.使用常量的好处 1.便于维护

  3. 第15章 上下文管理器和else块

    #<流流畅的Python>第15章 上下文管理器和else块 #15.1 先做这个,再做那个:if语句之外的else块 #else子句不仅能在if语句中使用,还能在for.while和tr ...

  4. Codeforces Round #541 (Div. 2) D 并查集 + 拓扑排序

    https://codeforces.com/contest/1131/problem/D 题意 给你一个n*m二维偏序表,代表x[i]和y[j]的大小关系,根据表构造大小分别为n,m的x[],y[] ...

  5. timerfd与eventfd

    1.timerfd timerfd是定时器描述符,通过timerfd_create()来创建它,timerfd_settime()来设置定时器时间,当时间到期定时器文件描述符就可读,所以能够在sele ...

  6. 如何在MYSQL下所有指定数据库名下执行SQL

    mysql下用户库比较多,都有统一的命名格式,希望在这些所有用户库执行脚本,更新数据,或者查询数据 可以采用以下存储过程实现 DROP PROCEDURE IF EXISTS `sp_execalld ...

  7. WordPaster-KesionCMS V8整合教程

    1.上传WordPaster文件夹 2.上传ckeditor3x插件文件夹 4.修改ckeditor编辑器的config.js文件,启用插件,在工具栏中增加插件按钮 5.在文章页面增加插件初始化代码 ...

  8. 大文件断点上传 js+php

    /* * js */ function PostFile(file, i, t) {    console.log(1);  var name = file.name, //文件名 size = fi ...

  9. bzoj2004(矩阵快速幂,状压DP)

    每个长度为p的区间都必须出现k次1,数据又很小,我们使用状压. dp[i][j]->dp[i+1][j'],dp[i][j]表示当前考虑到了第i个车站,包括第i个其后的p个的状态(有车停或没车停 ...

  10. _编程语言_C++_std

    正常使用 cout << "Count is "<<i<<endl; 含有std std::cout << "Count ...