QAQ 237行的代码 QAQ
打了我一个下午啊啊啊啊。
题目
我的做法
基础做法
机房大佬CLB提示我这道题目是虚树(当然我的做法不知道是不是虚树,反正虚就对了),于是我发现对于以下的图:
在对外显示上,两个蓝点的LCA是可以代替这两个蓝点的,然后,我们的做法出来了,对于两个点,维护他们两个的LCA,如果一个虚点(即不是真的异象石的位置)只有一个点(不管实的虚的),那么就将这个点连到其父亲上面。
特别的,因为(个人觉得)难以维护目前树上没有父亲的点,所以默认(1)点永远存在。(当然后来发现没有父亲的点最多一个,其实用个(root)存一下就行了)
当然,还是有不少细节的。
给上一个图(绿色为虚点,蓝色为实点,黑色为原树上的边,红色为虚树上的边):
注:一下内容的儿子、父亲、LCA可能有点难分辨是实树上的还是虚树上的,同时以下都以插入为例。
如何在log时间内查询一个点最近的父亲?
处理出DFS序后,我们新建了一个点,那么对于这个这个新建的点,如果是叶子节点的,那么整个子树(实树)直接认为其是父亲(用线段树实现),但是如果是非叶子节点,可以发现,他建立时最多只有两个儿子(虚树)节点(因为我们只要两个点有了LCA(实树)就建),然后把DFS序分成两段即可,但是需要注意的是,不一定是两个儿子,有时候只有一个儿子,因为LCA可能是其自己。
如何在log的时间内查询一个点的某个儿子的子树内的儿子节点编号
首先一个点的一个儿子(原树)的子树内的只可能存在一个儿子(虚树),两个的话他们必然会产生一个LCA,那么怎么查到这个点就是重中之重的事了,由于实树儿子的编号是固定的,我们可以在这个点的平衡树中插入其儿子的编号,而附加权值就是这个儿子子树中的虚树儿子编号。
实现
由于(1)一直都在,所以(zans)表示到(1)点的距离,根据要不要经过(1)决定加还是不加。(本人觉得(root)的实现会更简单吧)
当然只有亿点细节。
时间复杂度:(O(nlogn)),以及亿点点的常数,但也许是因为平衡树跑不满,或者因为正解用了set的缘故,我跑的竟然比正解快???
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<ctime>
#define N 110000
#define M 210000
using namespace std;
typedef long long LL;
//FHQ treap(平衡树)
int val[M],ke1[M],ke2[M],tot,rt[M],siz[N],son[M][2];bool li[N]/*这个点是否已经存在异象石*/;//最上层的点全部归到1
void spilt(int now,int k,int &A,int &B)
{
if(!now)A=B=0;
else
{
if(ke1[now]<=k)A=now,spilt(son[A][1],k,son[A][1],B);
else B=now,spilt(son[B][0],k,A,son[B][0]);
}
}
int mer(int A,int B)
{
if(!A || !B)return A+B;
if(val[A]<=val[B])son[A][1]=mer(son[A][1],B);
else son[B][0]=mer(A,son[B][0]),A^=B^=A^=B;
return A;
}
inline void add(int id,int k1,int k2)
{
tot++;val[tot]=rand();ke1[tot]=k1;ke2[tot]=k2;
int x,y;spilt(rt[id],k1,x,y);
rt[id]=mer(mer(x,tot),y);
}
inline void del(int id,int k)
{
int x,y,z;spilt(rt[id],k,x,z);spilt(x,k-1,x,y);
rt[id]=mer(x,z);
}
inline void change(int id,int k1,int k2)
{
int x,y,z;spilt(rt[id],k1,x,z);spilt(x,k1-1,x,y);
ke2[y]=k2;
rt[id]=mer(x,mer(y,z));
}
inline int findz(int id,int k)//这个子树是否存在虚树的儿子
{
int bk=0;
int x,y,z;spilt(rt[id],k-1,x,y);spilt(y,k,y,z);
if(y)bk=ke2[y];
rt[id]=mer(mer(x,y),z);
return bk;
}
//线段树
struct TREE
{
int l,r,c;
}tr[M];int tlen;
void bt(int l,int r)
{
int now=++tlen;
if(l<r)
{
int mid=(l+r)>>1;
tr[now].l=tlen+1;bt(l,mid);
tr[now].r=tlen+1;bt(mid+1,r);
}
else tr[now].c=1;
}
inline void set_c(int now,int k){tr[now].c=k;}
inline void downdate(int now)
{
if(tr[now].c)set_c(tr[now].l,tr[now].c),set_c(tr[now].r,tr[now].c),tr[now].c=0;
}
void CHG(int now,int l,int r,int ll,int rr,int k)
{
if(ll>rr)return ;
if(l==ll && r==rr){set_c(now,k);return ;}
downdate(now);
int mid=(l+r)/2;
if(rr<=mid)CHG(tr[now].l,l,mid,ll,rr,k);
else if(mid<ll)CHG(tr[now].r,mid+1,r,ll,rr,k);
else CHG(tr[now].l,l,mid,ll,mid,k),CHG(tr[now].r,mid+1,r,mid+1,rr,k);
}
int findans(int now,int l,int r,int id)
{
if(tr[now].c)return tr[now].c;
int mid=(l+r)/2;
if(id<=mid)return findans(tr[now].l,l,mid,id);
else return findans(tr[now].r,mid+1,r,id);
}
//边目录
struct node
{
int y,c,next;
}a[M];int len,last[N];
inline void ins(int x,int y,int c){len++;a[len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;}
//LCA
int fa[N][20],top,be[N],ll[N],rr[N],dep[N];
LL dis[N];
void dfs(int x)
{
++top;be[x]=top;ll[x]=top;
for(int i=1;i<=18;i++)
{
fa[x][i]=fa[fa[x][i-1]][i-1];
if(!fa[x][i])break;
}
for(int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if(y!=fa[x][0])dis[y]=dis[x]+a[k].c,dep[y]=dep[x]+1,fa[y][0]=x,dfs(y);
}
rr[x]=top;
}
int lca(int x,int y)
{
for(int i=18;i>=0;i--)
{
if(dep[fa[x][i]]>dep[y])x=fa[x][i];
}
return x;
}
int lcac(int x,int y)
{
if(dep[x]>dep[y])x^=y^=x^=y;
for(int i=18;i>=0;i--)
{
if(dep[fa[y][i]]>=dep[x])y=fa[y][i];
}
if(x==y)return y;
for(int i=18;i>=0;i--)
{
if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
}
return fa[x][0];
}
int n,m;
LL ans,zans/*表示连向1的点的距离,如果siz[1]>1则可以加入*/;
inline void del_dian(int x,int fat=0)//删除一个点,仅当这个点只有一个儿子的时候
{
if(li[x] || x==1)return ;//如果他只是单纯的自己一个点的话,或者说他是1的话,那没事了
if(!fat)fat=findans(1,1,n,be[fa[x][0]]);//找到x的父亲
int z=lca(x,fat),t=ke2[rt[x]]/*找到其唯一的一个节点*/;
//删除x
del(x,ke1[rt[x]]);siz[x]=0;
CHG(1,1,n,ll[x],ll[t]-1,fat);
CHG(1,1,n,rr[t]+1,rr[x],fat);
//插入
change(fat,z,t);
if(fat==1)
{
ans-=dis[t]-dis[x];
zans+=dis[t]-dis[x];
}
}
int main()
{
srand(666);
dep[0]=-1;
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int x,y,c;scanf("%d%d%d",&x,&y,&c);
ins(x,y,c);ins(y,x,c);
}
dfs(1);
scanf("%d",&m);
bt(1,n);
for(int i=1;i<=m;i++)
{
char st[10];scanf("%s",st);
if(st[0]=='+')
{
int x;scanf("%d",&x);
if(!li[x])//插入点
{
li[x]=1;siz[x]++;//他本身自己就是一个点
int y=findans(1,1,n,be[x])/*表示父亲节点*/;
if(x!=y)
{
int z=lca(x,y)/*y的下面*/,t/*和x结合的点*/;
if((t=findz(y,z)))//什么,有点可以结合
{
//先把x,t插入到k中
int k=lcac(t,x);//新建立一个点代表他们两个
int shit;
if(k!=x)shit=lca(x,k),add(k,shit,x);//自己怎么能加入自己呢?
shit=lca(t,k),add(k,shit,t);//t肯定不等于k,否则x的父亲就是t了
siz[k]=2;//有两个点
CHG(1,1,n,ll[k],ll[t]-1,k);//你们的父亲都是我
CHG(1,1,n,rr[t]+1,rr[k],k);//你们的父亲都是我
//此时的k已经可以等效x,t了
change(y,z,k);//将原本t的位置改成k
if(y==1)
{
ans+=dis[t]+dis[x]-2*dis[k];
zans+=dis[k]-dis[t];
}
else ans+=dis[x]-dis[k];
if(k!=x)CHG(1,1,n,ll[x],rr[x],x);
}
else
{
add(y,z,x),siz[y]++;//没有就加入
if(y!=1)ans+=dis[x]-dis[y];
else zans+=dis[x]-dis[y];
CHG(1,1,n,ll[x],rr[x],x);
}
}
}
}
else if(st[0]=='-')
{
int x;scanf("%d",&x);
if(li[x])//插入点
{
int y=findans(1,1,n,be[fa[x][0]])/*表示父亲节点*/;
li[x]=0;siz[x]--;
if(!siz[x])//只有一个点
{
CHG(1,1,n,ll[x],rr[x],y);
int z=lca(x,y);del(y,z);siz[y]--;
if(y==1)zans-=dis[x];
else ans-=dis[x]-dis[y];
if(siz[y]==1)del_dian(y);
}
else if(siz[x]==1)del_dian(x,y);//只剩下子树中的一个点,破坏掉这个原有的结构
}
}
else
{
printf("%lld
",ans+zans*(siz[1]>1));
}
}
return 0;
}
正解
先求出树上点的DFS序,将异象石按DFS序排序,连成一个环,然后将相邻两个异象石距离相加,得到的就是答案的两倍。
严谨参照 https://www.cnblogs.com/2aptx4869/p/13513541.html (虽然我并不知道为什么他还要枚举(a_3)和(a_2)在树上的相对位置)
但是我的证明是,将其像我的做法一样处理(但是(1)不是严格存在了,严格存在只是因为代码难处理)
然后得到了一棵虚树,不难想到一定存在一种遍历方案,使得实树中按异象石的DFS排列后的异象石序列,和虚树中按异象石的DFS排列的异象石序列相同。(简单来说就是在虚树中遍历跟在实树中遍历是一样的,所以这个证明放到实树遍历一样的,虚树更加直观)。
接下来我会证明,这个加法的过程,其实就是(DFS)遍历中进入和回溯时经过的边的权值的总和。
对于一个实点而言,你在遍历的过程中不管是回溯还是进入,你遍历到的下一个实点,就是你DFS序的后一位,而进入回溯所记录的边的总和,就是你们两个的距离(否则中间必然存在其他实点,因为叶子节点都是实点)。
不过刚开始从根节点走到(1) 号实点以及(n)号实点回溯到根节点记录的边权,你可以认为加起来是(n)号实点在找(1)号实点。
同时这个虚树所有边的权值就是答案,而DFS所记录的边权其实就是所有边权值和的两倍。
证毕。
至于维护DFS序的排序,用的是set。
代码也是抄的QMQ。
时间复杂度:(O(nlogn))
#include <bits/stdc++.h>
#define all(n) (n).begin(), (n).end()
#define se second
#define fi first
#define pb push_back
#define mp make_pair
#define sqr(n) (n)*(n)
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
#define IO ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr)
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<ll, ll> PLL;
typedef vector<int> VI;
typedef double db;
const int N = 1e5 + 5;
int n, m, _, k;
int h[N], ne[N << 1], to[N << 1], co[N << 1], tot;
int dfn[N], cnt, d[N], f[N][30], t;
ll dist[N], ans;
char s[3];
set<PII> st;
void add(int u, int v, int c) {
ne[++tot] = h[u]; h[u] = tot; to[tot] = v; co[tot] = c;
}
void bfs(int s) {
queue<int> q;
q.push(s); d[s] = 1; dist[s] = 0;
rep (i, 0, t) f[s][i] = 0;
while (!q.empty()) {
int x = q.front(); q.pop();
for (int i = h[x]; i; i = ne[i]) {
int y = to[i];
if (d[y]) continue;
d[y] = d[x] + 1;
dist[y] = dist[x] + co[i];
f[y][0] = x;
for (int j = 1; j <= t; ++j)
f[y][j] = f[f[y][j - 1]][j - 1];
q.push(y);
}
}
}
int lca(int x, int y) {
if (d[x] > d[y]) swap(x, y);
per (i, t, 0)
if (d[f[y][i]] >= d[x]) y = f[y][i];
if (x == y) return x;
per (i, t, 0)
if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
void dfs(int u) {
dfn[u] = ++cnt;
for (int i = h[u]; i; i = ne[i]) {
int y = to[i];
if (dfn[y]) continue;
dfs(y);
}
}
void worka() {
auto it = st.upper_bound({ dfn[k], k });
int x, y;
if (it == st.begin() || it == st.end()) x = st.begin()->se, y = st.rbegin()->se;
else x = it->se, y = (--it)->se;
ans += (dist[k] << 1) - ((dist[lca(k, x)] + dist[lca(k, y)] - dist[lca(x, y)]) << 1);
st.insert({ dfn[k], k });
}
void workb() {
if (st.size() == 1) { st.clear(); return; }
auto it = st.lower_bound({ dfn[k], k }), ita = it; ++ita;
int x, y;
if (ita == st.end()) y = st.begin()->se;
else y = ita->se;
if (it == st.begin()) x = st.rbegin()->se;
else x = (--it)->se;
ans -= (dist[k] << 1) - ((dist[lca(k, x)] + dist[lca(k, y)] - dist[lca(x, y)]) << 1);
st.erase(--ita);
}
int main() {
IO; cin >> n;
rep(i, 2, n) {
int u, v, c; cin >> u >> v >> c;
add(u, v, c); add(v, u, c);
}
t = log2(n - 1) + 1;
dfs(1); bfs(1);
cin >> m;
rep(i, 1, m) {
cin >> s;
if (s[0] == '?') cout << (ans >> 1) << '
';
else {
cin >> k;
if (st.empty()) st.insert({ dfn[k], k });
else if (s[0] == '+') worka();
else workb();
}
}
return 0;
}