题目传送门
这道题跟我之前做的Qtree4几乎是一样的。
都是神题。
其实我很想膜一下出题人的。
太强了。
详细解法还是看我的博客吧。
再写要写死人了。
Qtree4博客
不会动态点分治的话还是一定要看我这篇博客的(毕竟我写的很辛苦。)
这道题与Qtree4唯一的不同就是边权为1。
每次dep+1就好。
代码实现(Qtree4代码改的):
#include<set>
#include<map>
#include<ctime>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define inf 1000000000
#define mod 1000000007
#define pa pair<int,int>
#define ll long long
using namespace std;
int bin[20],Log[200005];//bin[i]表示2的i次方,Log就是log
int n,m,G,cnt,dfn,sum,tott;
int tot[100005],f[100005],dep[100005];// f[i]数组表示去掉点i之后剩下所有子树家族最大的子树的家族数目
//dep表示到一号节点的距离
int mn[18][200005],ys[100005],fa[100005];//mn[i][j]表示dfs序中从j到j+2^i-1的最小深度的点
bool v[100005],col[100005];//col表示当前这个点的颜色,1为黑色,0为白色
struct node{
int x,y,next;
}a[200005];int len,last[100005];
void ins(int x,int y) {
len++;
a[len].x=x;a[len].y=y;
a[len].next=last[x];last[x]=len;
}
/*
做法。
对于每个节点我们维护两个堆(堆的堆顶最大)
每个节点的第一个堆维护所有子树内的节点到自己父亲节点的距离
第二个堆维护所有子节点第一个堆的堆顶(最大值)
那么相对于每一个节点来说,第二个堆的最大值加次大值就是子树内经过这个节点的最长链!!
全局维护一个堆,记录所有节点第二个堆的最大值和次大值的和。堆顶(最大值)就是答案
*/
/*
堆的操作;
push(x),把x加进堆里
pop(),删除堆顶
top(),询问堆顶
size(),堆里有多少个数
*/
struct heap{
priority_queue<int> A,B;//A堆记录全部的状态,B堆记录没用的状态
void push(int x){ //加进一个目前有用的状态(将来可能没用了)
A.push(x);
}
void erase(int x){ //更好的理解为删除这个状态
B.push(x);
}
void pop(){ //删除堆顶
while(B.size()&&A.top()==B.top()) //首先要把没用的状态给删除了
A.pop(),B.pop();
A.pop(); //去掉没用的状态后删除堆顶
}
int top(){ //取堆顶
while(B.size()&&A.top()==B.top())// 删除没用的状态
A.pop(),B.pop();
if(!A.size())return 0;
return A.top();
}
int size(){ //全部的状态数-没用的状态数=有用的状态数
return A.size()-B.size();
}
int stop(){ //求堆里的次大值
if(size()<2)return 0; //如果有用的状态数小于2说明没有次大值
int x=top();pop(); //先把堆顶删除
int y=top();push(x);//删除原来的堆顶之后剩下的堆顶就是原来的次大值!得到次大值之后再把原来的堆顶加回去
return y;
}
}A,B[110000],C[110000];//C数组表示每个节点的第一个堆,B数组表示第二个堆,A表示全局的堆
void dfs(int x,int fa) //求出dfs序和dep数组
{
mn[0][++dfn]=dep[x];
ys[x]=dfn;
for(int k=last[x];k;k=a[k].next) {
int y=a[k].y;
if(y!=fa)
{
dep[y]=dep[x]+1;
dfs(y,x);
mn[0][++dfn]=dep[x];
}
}
}
/*
getrt
找到树的重心并存在G里。
树的重心是一个点
删掉树的重心之后所剩子树家族最大的最小
*/
void getrt(int x,int fa)
{
tot[x]=1;f[x]=0;
for(int k=last[x];k;k=a[k].next) {
int y=a[k].y;
if(y!=fa&&v[y]==false)
{
getrt(y,x);
tot[x]+=tot[y];
f[x]=max(f[x],tot[y]);
}
}
f[x]=max(f[x],sum-tot[x]); // 把下面的子树问完以后还有以父亲节点为根的那棵子树
if(f[x]<f[G]) // G记录的就是当前子树的重心
G=x;
}
//按照新的树去建父子关系
void divi(int x)
{
v[x]=true;
for(int k=last[x];k;k=a[k].next) {
int y=a[k].y;
if(v[y]==false)
{
sum=tot[y];G=0;
getrt(y,x); //找出这棵子树的重心
fa[G]=x;divi(G); //下一层的重心链接上一层的重心,然后继续去建新的树
}
}
}
//求x到y之间深度最小的点的深度
int rmq(int x,int y)
{
x=ys[x];y=ys[y];
if(x>y)
swap(x,y);
int t=Log[y-x+1];
return min(mn[t][x],mn[t][y-bin[t]+1]);//ST表求解公式
}
int dis(int x,int y) //原树上x到y的距离
{
return dep[x]+dep[y]-2*rmq(x,y);
}
//每个节点的堆都是维护下面的节点。
//那么改变一个节点的颜色就只会对它上面的节点有影响。
//把v节点关上,v对u节点有影响。
void turn_off(int u,int v)
{
if(u==v)
{
B[u].push(0);
if(B[u].size()==2)A.push(B[u].top());
}
if(!fa[u])return;
int f=fa[u],D=dis(f,v),tmp=C[u].top();
C[u].push(D);//把v节点改为黑色,那么这个距离一定是有用的。
if(D>tmp) //如果现在的距离优于原来的最大距离。那么有可能对我的答案有影响。
{
int mx=B[f].top()+B[f].stop(),size=B[f].size();//mx记录原来的答案
if(tmp)B[f].erase(tmp);//tmp已经不是堆顶了,不能被记录在B[f]里。要删除
B[f].push(D); //D成为了堆顶 ,要被记录在B[f]里。
int now=B[f].top()+B[f].stop(); //当前的答案
if(now>mx) //如果当前答案优于原来的答案
{
if(size>=2)A.erase(mx); //size>=2说明你有最大值和次大值,mx已经不是最优答案了,要把他删除
if(B[f].size()>=2)A.push(now);//now是当前最优答案,要记录进全局堆
}
}
turn_off(f,v);// 往上更新
}
//把v节点打开,v对于u有影响
void turn_on(int u,int v)//跟turn_off思想差不多
{
if(u==v)
{
if(B[u].size()==2)A.erase(B[u].top());
B[u].erase(0);
}
if(!fa[u])return;
int f=fa[u],D=dis(f,v),tmp=C[u].top();
C[u].erase(D);//D肯定没有用了,因为v变成了白色
if(D==tmp)//如果我等于原来的堆顶,那么有可能删掉的就是堆顶(堆顶有可能有多个重复),那么就有可能对我的答案有影响
{
int mx=B[f].top()+B[f].stop(),size=B[f].size();
B[f].erase(D);//很明显D没用了要删除。
if(C[u].top())B[f].push(C[u].top());//如果你的堆顶不为零,那么我要记录一下。
int now=B[f].top()+B[f].stop();//现在的答案
if(now<mx) //原来的答案经过更新之后变小了,说明改变v是对f的答案有影响的,那么我要更新全局
{
if(size>=2)A.erase(mx); // 不解释了
if(B[f].size()>=2)A.push(now);
}
}
turn_on(f,v);
}
int main()
{
bin[0]=1;for(int i=1;i<20;i++)bin[i]=bin[i-1]<<1;
Log[0]=-1;for(int i=1;i<=200000;i++)Log[i]=Log[i>>1]+1;
scanf("%d",&n);
len=0;memset(last,0,sizeof(last));
for(int i=1;i<n;i++)
{
int x,y;scanf("%d%d",&x,&y);
ins(x,y);ins(y,x);
}
dfs(1,0);
// mn[i][j]表示j到j+2^i-1最小深度的点
// ST表模板
for(int i=1;i<=Log[dfn];i++)
for(int j=1;j<=dfn;j++)
if(j+bin[i]-1<=dfn)
mn[i][j]=min(mn[i-1][j],mn[i-1][j+bin[i-1]]);
G=0;f[0]=inf;sum=n;
getrt(1,0);
fa[G]=0;divi(G);
for(int i=1;i<=n;i++)col[i]=1;//col[i]=1时表示该节点为关着的,=0时为开着的。
for(int i=1;i<=n;i++)
{
turn_off(i,i);
tott++;//tott表示关灯房间数量数量
}
char ch[2];
scanf("%d",&m);
while(m--)
{
scanf("%s",ch+1);
if(ch[1]=='G')
{
if(tott<=1)printf("%d\n",tott-1);
else printf("%d\n",A.top());
}
else
{
int x;scanf("%d",&x);
if(col[x])turn_on(x,x),tott--;
else turn_off(x,x),tott++;
col[x]^=1;
}
}
return 0;
}
这两题都神。
我写那么详细就是为了让别人更容易学。
因为我学的时候大神只有几句解释我这种蒟蒻理解不了啊![苦逼]