题面
英文题面
题意:给定一棵树,维护以下3个操作:
- 1 \(x\)表示如果节点\(x\)为白色,则将其染黑。否则对这个节点的所有儿子递归进行相同操作
- 2 \(x\)表示将以节点\(x\)为root的子树染白。
- 3 \(x\)表示查询节点\(x\)的颜色。
\(n,q \leq 10^5\)。
题解:考虑一种优雅的暴力------将所有询问分块。真是涨姿势了
由于所有操作都是针对单点的,我们可以在一个块的操作中只考虑这些询问涉及到的点,这样复杂度就降到了块长级别。然后做完后再\(O(n)\)的考虑对原树中其他节点的影响。将块长设为\(\sqrt q\),那么总复杂度就是\(O(n\sqrt n)\)的。
具体来说,我们先建出虚树,注意对每条边存一下边长和这条边上黑点的个数。然后对于1操作,直接暴力做就行,如果当前这条边上所有点都是黑的,就往下递归就行。对于2操作,暴力清空即可。3操作就直接输出,没啥好说的。
在还原这些操作对原树的节点的影响时,我们需要在上述操作中对虚树上的每个点记录两个值:\(cnt_i,sn_i\)分别表示当前节点1操作做了几次对它有影响,当前节点在这些操作中是否是2操作的点的子树里的一个点。DFS一遍就行了。
还有一种树剖的做法,大概是考虑每个\(x\)的祖先\(y\)对它能否造成贡献,把式子搞出来,然后提取出只与\(y\)相关的部分,用线段树维护即可。注意2操作时不仅要将子树清空,还要考虑祖先节点不能对其造成贡献。时间复杂度是俩log。
代码:
#include
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
typedef long long ll;
#define I inline void
#define IN inline int
#define C(x,y) memset(x,y,sizeof(x))
#define STS system("pause")
templateI read(D &res){
res=0;register D g=1;register char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')g=-1;
ch=getchar();
}
while(isdigit(ch)){
res=(res<<3)+(res<<1)+(ch^48);
ch=getchar();
}
res*=g;
}
int n,m,fa[101000],t[101000],v[101000],dfn[101000],dep[101000],siz[101000],id[101000],_tot,lg[202000],xu[202000],f[202000][20],cnt;
int len,b[101000],sum,ori[101000],clr[101000],sn[101000],ct[101000];
inline bool bbb(int x,int y){return dfn[x]y)swap(x,y);
re len=lg[y-x+1];return ckmin(f[x][len],f[y-(1<1)add(st[q-1],st[q]),q--;
F(i,1,n)ori[i]=clr[i];
}
I sign(int x){
// cout<<"%"<e[101000];
I D_1(int x,int depth){
dfn[x]=++_tot;xu[++cnt]=x;id[x]=cnt;siz[x]=1;dep[x]=depth;
// cout<>1]+1;
F(j,1,lg[cnt])F(i,1,cnt-(1<