现在他想解决这样一个问题:给定一颗有根树(根为1),有以下两种操作:

标记操作:对某个结点打上标记(在最开始,只有结点1有标记,其他结点均无标记,而且对于某个结点,可以打多次标记)。
询问操作:询问某个结点最近的一个打了标记的祖先(这个结点本身也算自己的祖先)你能帮帮他吗?

树剖

但是暴力能过去

#include <bits/stdc++.h>
constexpr int maxn = 100050;
int fa[maxn];
bool flag[maxn];
int t[maxn], nearfa[maxn], cnt = 1;

int find(int u) {
    if (flag[u])
        return u;
    if (t[u] == cnt)
        return nearfa[u];
    t[u] = cnt;
    return nearfa[u] = find(fa[u]);
}

int main(void) {
    flag[1] = 1;
    fa[1] = 1;
    int n, q;
    scanf("%d%d", &n, &q);
    for (int i = 1; i < n; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        fa[v] = u;
    }
    for (int i = 1; i <= n; i++) t[i] = nearfa[i] = 1;
    for (int i = 0; i < q; i++) {
        char oper;
        int u;
        scanf("\n%c %d", &oper, &u);
        if (oper == 'C') {
            if (!flag[u])
                flag[u] = 1;
            cnt++;
        } else
            printf("%d\n", find(u));
    }
    return 0;
}

数据水爆了,可以被卡到\(n^2\),但是跑的比正解还快。

梦幻布丁

\(n\) 个布丁摆成一行,进行 \(m\) 次操作。每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色。

例如,颜色分别为 \(1,2,2,1\) 的四个布丁一共有 \(3\) 段颜色.

我们可以把每种颜色的布丁集合想象成是一个队列,每次操作合并,显然暴力是n的,但是可以启发式合并,把短的合并到长的上去,复杂度就变成了log。
我们先求出原序列的答案,对于每一种颜色都用类似链表的数据结构串起来,并记录下尾节点。每次修改,都根据启发式合并的方法来暴力合并,然后处理一下此次合并对答案的影响(显然答案是不增的)。
image

#include <iostream>
#include <cstdio>

const int N=1e5+5,M=1e6+5;
int n,m,c[N],sz[M],st[M],f[M],hd[M],nxt[N],ans;

void merge(int x,int y) {
    for(int i=hd[x];i;i=nxt[i]) ans-=(c[i-1]==y)+(c[i+1]==y);
    for(int i=hd[x];i;i=nxt[i]) c[i]=y;
    nxt[st[x]]=hd[y],hd[y]=hd[x],sz[y]+=sz[x];
    hd[x]=st[x]=sz[x]=0;
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i) {
        scanf("%d",&c[i]),f[c[i]]=c[i];
        ans+=c[i]!=c[i-1];
        if(!hd[c[i]]) st[c[i]]=i;
        ++sz[c[i]],nxt[i]=hd[c[i]],hd[c[i]]=i;
    }
    while(m--) {
        int opt;
        scanf("%d",&opt);
        if(opt==2) printf("%d\n",ans);
        else {
            int x,y;
            scanf("%d%d",&x,&y);
            if(x==y) continue;
            if(sz[f[x]]>sz[f[y]]) std::swap(f[x],f[y]);
            if(!sz[f[x]]) continue;
            merge(f[x],f[y]);
        }
    }
    return 0;
}

软件包管理器

你决定设计你自己的软件包管理器。不可避免地,你要解决软件包之间的依赖问题。如果软件包 \(a\) 依赖软件包 \(b\),那么安装软件包 \(a\) 以前,必须先安装软件包 \(b\)。同时,如果想要卸载软件包 \(b\),则必须卸载软件包 \(a\)

现在你已经获得了所有的软件包之间的依赖关系。而且,由于你之前的工作,除 \(0\) 号软件包以外,在你的管理器当中的软件包都会依赖一个且仅一个软件包,而 \(0\) 号软件包不依赖任何一个软件包。且依赖关系不存在环(即不会存在 \(m\) 个软件包 \(a_1,a_2, \dots , a_m\),对于 \(i<m\)\(a_i\) 依赖 \(a_{i+1}\),而 \(a_m\) 依赖 \(a_1\) 的情况)。

现在你要为你的软件包管理器写一个依赖解决程序。根据反馈,用户希望在安装和卸载某个软件包时,快速地知道这个操作实际上会改变多少个软件包的安装状态(即安装操作会安装多少个未安装的软件包,或卸载操作会卸载多少个已安装的软件包),你的任务就是实现这个部分。

注意,安装一个已安装的软件包,或卸载一个未安装的软件包,都不会改变任何软件包的安装状态,即在此情况下,改变安装状态的软件包数为 \(0\)


每次安装软件,就把根节点到x软件路径上的值全部变为1

同理,每次卸载软件,就把x以及它的子树的值变为0

故我们可以用区间和的思想,每次操作之前记录一下tree[root].sum的值,更新之后再查询一遍tree[root].sum的值,两者之差的绝对值则为答案。

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn =2e5+10;
int n,k,x;
int head[maxn],deep[maxn],father[maxn],size[maxn];
int dis[maxn],top[maxn],son[maxn];
int q,tidnum;
int pos[maxn],tid[maxn];
struct node{
	int to,next;
}edge[maxn<<2];
struct Node{
	int left,right,flag,sum;
}tree[maxn<<2];
void addo(int u,int v){
	edge[++k].to = v;
	edge[k].next = head[u];
	head[u] = k;
}
void dfs1(int x,int fa,int depth){
	size[x] = 1,father[x] = fa,deep[x] = depth;
	for(int i = head[x];i;i = edge[i].next){
		if(edge[i].to == fa) continue;
		dfs1(edge[i].to,x,depth+1);
		size[x] += size[edge[i].to];
		if(!son[x] || size[edge[i].to] > size[son[x]]) son[x] = edge[i].to;
	}
}
void dfs2(int x,int tp){
	tid[x] = ++tidnum;
	pos[tid[x]] = x;
	top[x] = tp;
	if(!son[x]) return;
	dfs2(son[x],tp);
	for(int i = head[x];i;i = edge[i].next){
		if(edge[i].to != son[x] && edge[i].to != father[x]){
			dfs2(edge[i].to,edge[i].to);
		}
	}
}
inline int ls(int x){return x<<1;};
inline int rs(int x){return x<<1|1;};
void build(int id,int l,int r){
	tree[id].left = l;tree[id].right = r;
	tree[id].sum = 0,tree[id].flag = -1;
	if(l == r) return;
	int mid = (l + r) >> 1;
	build(ls(id),l,mid),build(rs(id),mid+1,r);
	return;
}

void downdata(int id)
{
	tree[id<<1].sum=(tree[id<<1].right-tree[id<<1].left+1)*tree[id].flag;
	tree[id<<1|1].sum=(tree[id<<1|1].right-tree[id<<1|1].left+1)*tree[id].flag;
	tree[id<<1].flag=tree[id<<1|1].flag=tree[id].flag;
	tree[id].flag=-1;
}
int get(int id,int l,int r)
{
	if(tree[id].right<l||tree[id].left>r) return 0;
	if(tree[id].right<=r&&tree[id].left>=l) return tree[id].sum;
	if(tree[id].flag!=-1) downdata(id);
	return get(id<<1,l,r)+get(id<<1|1,l,r);
}
void update(int id,int l,int r,int val)
{
	if(tree[id].right<l||tree[id].left>r) return;
	if(tree[id].right<=r&&tree[id].left>=l)
	{
		tree[id].sum=(tree[id].right-tree[id].left+1)*val;
		tree[id].flag=val;
		return;
	}
	if(tree[id].flag!=-1) downdata(id);
	update(id<<1,l,r,val);update(id<<1|1,l,r,val);
	tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
	return;
}
void change(int u,int v,int val)
{
	while(top[u]!=top[v])
	{
		if(deep[top[u]]<deep[top[v]]) std::swap(u,v);
		update(1,tid[top[u]],tid[u],val);
		u=father[top[u]];
	}
	if(deep[u]>deep[v]) std::swap(u,v);
	update(1,tid[u],tid[v],val);
	return;
}

int main(){
	ios::sync_with_stdio(0);
	cin>>n;
	for(int i = 2;i <= n;i++){
		cin>>x;
		x++;
		addo(x,i);
	}
	dfs1(1,1,1),dfs2(1,1);
	cin>>q;
	build(1,1,tidnum);
	while(q --> 0){
		string s;
		cin>>s>>x;
		x++;
		int t1 = tree[1].sum;
		if(s[0] == 'i'){
			change(1,x,1);
			int t2 = tree[1].sum;
			cout<<abs(t2 - t1)<<endl;
		}
		else{
			update(1,tid[x],tid[x] + size[x] - 1,0);
			int t2 = tree[1].sum;
			cout<<abs(t1 - t2)<<endl;
		}
	}
	return 0;
}