题目大意
给出一棵树,初始全为黑点。执行若干操作,操作1:将某个点的颜色取反 操作2:询问最远的两个黑点的距离。
思路
考虑动态点分治,用可删堆维护一些链长即可,细节较多,十分累人。
具体:先和普通点分治一样,确定树的划分方案,然后每一棵子树的重心u和上面一层的重心Fa[u]连边,构成一棵分治树T。预处理出每个点的信息dis[i][j],表示i到分治树上深度为j的祖先的实际距离。然后分治时,每个点u管辖的路径就是所有经过它的路径,考虑维护第一长和第二长(不经过同一子树),并且维护当前子树u中深度最深的点的深度(以Fa[u]为根时)Deep[u]。由于这些信息时刻会更新、删除,所以用到了可删堆。
可删堆:支持logn删除任意元素的特殊堆。实现很简单:开两个堆a和b,a存放原来堆中的元素,b用来存放待删除的元素。每一次访问堆a的最小值时,比较a.top()和b.top(),如果一样,说明a.top()已经是不存在的元素了,执行a.pop(),b.pop()。总之,核心思想就是先不忙删除,先放到b中,等到要用的时候再判断。
复杂度O(nlog^2n),有点卡常,并不是标算。BZOJ上过掉了,洛谷上80分,TLE两个点。嘛,本题就是用来练动态点分治的,超时也无所谓了。
更新的时候,在分治树上不断往上跳,更新每个祖先的相关信息,然后更新存放的答案的那个堆即可,具体细节看程序吧,说不清楚。
p.s. 一开始漏算了直接以u为端点的情况,结果还拍不出错,差点调到恍惚……(╯‵□′)╯︵┻━┻
#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
#define rep(i,j,k) for (i=j;i<=k;i++)
#define down(i,j,k) for (i=j;i>=k;i--)
using namespace std;
const int N=1e5+5,K=18,INF=1e9;
int n,m,u,v,i,total,Min,root,cnt,tmp,lit[N],vis[N],size[N];
int lv[N],Fa[N],dis[N][K];
int En,fst[N],to[N*2],nxt[N*2];
char od;
struct Del_heap{
priority_queue<int> a,b; int tot;
int top() {
while (!a.empty() && !b.empty() && a.top()==b.top()) a.pop(),b.pop();
if (a.empty()) return -INF; //mistake3:原来是return 0,结果产生了不合法解:只有一个子树有黑屋,最深为a,结果拼出a+0这种不合法路径
return a.top();
}
void push(int x) {
a.push(x); tot++;
}
void del(int x) {
if (!tot) return ;
b.push(x); tot--;
}
}ans,sc[N],Deep[N]; int fs[N]; //mistake1:Deep的意义搞错了
void read(int &ret)
{
char ch; ret=0;
for (ch=getchar();ch<'0' || ch>'9';ch=getchar());
for (;ch>='0' && ch<='9';ch=getchar()) ret=ret*10+ch-'0';
}
void add(int u,int v) {
En++; nxt[En]=fst[u]; fst[u]=En; to[En]=v;
}
void get_size(int x)
{
int j; vis[x]=1; size[x]=1;
for (j=fst[x];j;j=nxt[j])
if (!vis[to[j]]) {
get_size(to[j]);
size[x]+=size[to[j]];
} vis[x]=0;
}
void get_root(int x)
{
int j,maxsize=total-size[x]; vis[x]=1;
for (j=fst[x];j;j=nxt[j])
if (!vis[to[j]]) {
get_root(to[j]);
maxsize=max(maxsize,size[to[j]]);
}
if (maxsize<Min) Min=maxsize,root=x;
vis[x]=0;
}
void get_dep(int x,int depth,int LV)
{
int j; vis[x]++;
dis[x][LV]=depth;
if (LV>0) Deep[root].push(dis[x][LV-1]);
for (j=fst[x];j;j=nxt[j])
if (!vis[to[j]]) get_dep(to[j],depth+1,LV);
vis[x]--;
}
void updata(int wh,int data)
{
if (fs[wh]<=0) { //mistake4:没有特判这种情况,导致0加入了sc堆
fs[wh]=data; return ;
}
if (data>fs[wh]) {
sc[wh].push(fs[wh]);
fs[wh]=data;
return ;
}
sc[wh].push(data);
}
void remove(int wh,int data)
{
if (data==fs[wh]) {
fs[wh]=sc[wh].top();
sc[wh].del(sc[wh].top());
return ;
}
sc[wh].del(data);
}
void divide(int u,int father,int LV,int &rt)
{
get_size(u); total=size[u];
int j,son; Min=n; get_root(u); rt=root;
vis[rt]=1; lv[rt]=LV;
get_dep(rt,0,LV);
Fa[rt]=father;
for (j=fst[rt];j;j=nxt[j])
if (!vis[to[j]]) {
divide(to[j],rt,LV+1,son);
updata(rt,Deep[son].top()); //mistake2:儿子搞错了,写成了to[j]
}
ans.push(fs[rt]+sc[rt].top());
ans.push(fs[rt]);
}
void Init()
{
read(n); cnt=n;
rep(i,1,n-1)
{
read(u); read(v);
add(u,v); add(v,u);
}
divide(1,0,0,tmp);
}
void change(int u)
{
int pos=u,fa,dp;
if (lit[u]) cnt++; else cnt--;
lit[u]^=1;
if (lit[u]) ans.del(fs[u]); else ans.push(fs[u]);
while (1)
{
fa=Fa[pos];
if (!fa) break;
dp=dis[u][lv[pos]-1];
ans.del(fs[fa]+sc[fa].top());
if (!lit[fa]) ans.del(fs[fa]);
remove(fa,Deep[pos].top());
if (!lit[u]) Deep[pos].push(dp);
else Deep[pos].del(dp);
updata(fa,Deep[pos].top());
if (!lit[fa]) ans.push(fs[fa]);
ans.push(fs[fa]+sc[fa].top());
pos=Fa[pos];
}
}
void print(int x)
{
int bn=0,i,b[10];
if (!x) putchar('0');
else {
while (x>0) b[++bn]=x%10,x/=10;
down(i,bn,1) putchar('0'+b[i]);
}
putchar('\n');
}
void Work()
{
int query=0;
read(m);
while (m--)
{
od=getchar();
if (od=='C')
{
read(u);
change(u);
}
else {
od=getchar();
query++;
if (cnt==1) print(0);
else if (cnt==0) printf("-1\n");
else print(ans.top());
}
}
}
int main()
{
Init();
Work();
return 0;
}