虚树学习笔记

虚树是解决一类树上问题的强力工具。
基本上是多组询问,每组询问给出一组特殊点,让你完成某个任务。。。。

将所有特殊点按照原树的dfs序排序,然后依次枚举每个特殊点,用增量法创建这组询问的虚树。
我们创建的时候维护一个栈,栈里的元素构成一条链。
当我们新加入一个点v的时候,先求出v与栈顶元素的lca。因为我们要保证栈中的元素是构成一条链的嘛。然后就要把 ,不在lca->v这条链上的点都弹出去。然后把v入栈。
中间弹点的时候记得要加边。

luoguP2495 [SDOI2011]消耗战
把虚树建出来之后直接dp即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
#define fi first
#define se second
#define pb push_back
#define wzh(x) cerr<<#x<<'='<<x<<endl;
struct Virtual_tree {
  vector<int>v[N], g[N];
  int dep[N], f[N][22], st[N], top;
  int n, r, dfn[N], CNT;
  void init(int _n, int _r) {
    n = _n; r = _r; CNT = 0;
    for (int i = 1; i <= n; i++) {
      v[i].clear();
      g[i].clear();
    }
  }
  int lca(int u, int v) {
    if (dep[u] < dep[v])swap(u, v);
    for (int i = 19; i >= 0; i--)if ((dep[u] - dep[v]) >> i & 1)u = f[u][i];
    if (u == v)return u;
    for (int i = 19; i >= 0; i--)if (f[u][i] != f[v][i])u = f[u][i], v = f[v][i];
    return f[u][0];
  }
  void dfs(int x, int y) {
    f[x][0] = y; dfn[x] = ++CNT;
    dep[x] = dep[y] + 1;
    for (int j = 1; j <= 19; j++) {
      f[x][j] = f[f[x][j - 1]][j - 1];
    }
    for (auto k : v[x])if (k != y) {
        dfs(k, x);
      }
  }
    void ins(int u) {
      if(top==1){st[++top]=u;return;}
      int lc=lca(u,st[top]);
      if(dep[lc]==dep[st[top]]){st[++top]=u;return;}
      while(top>1 && dep[lc]<=dep[st[top-1]]){
        g[st[top-1]].pb(st[top]);
        top--;
      }
      if(dep[lc]!=dep[st[top]]){
        g[lc].pb(st[top]);
        st[top]=lc;
      }
      st[++top]=u;
    }
  
  void in(vector<int>&da) {
    sort(da.begin(), da.end(), [&](int x, int y) {return dfn[x] < dfn[y];});
    top = 0; st[++top] = r;
    for (auto k : da) {
      if (k != 1)ins(k);
    }
    while (top > 1) {
      g[st[top - 1]].pb(st[top]);
      top--;
    }
  }
  int dis(int x, int y) {
    return dep[x] + dep[y] - 2 * dep[lca(x, y)];
  }
} G;
int n,m;
LL cost[N];
vector<pair<int,LL>>v[N];
void _dfs(int x,int y,LL z){
  cost[x]=z;
  for(auto k:v[x]){
    if(k.fi!=y){
      _dfs(k.fi,x,min(z,k.se));
    }
  }
}
int vis[N];
LL dfs(int x,int y){
  if(vis[x]) {
    return cost[x];
  }
  LL res=0;
  for(auto k:G.g[x]){
    res+=min(1ll*cost[k],dfs(k,x));
  }
  return min(res,cost[x]);
}
void cl(int x){
  for(auto k:G.g[x]){
    cl(k);
  }
  G.g[x].clear();
}
int main() {
  scanf("%d",&n);
  G.init(n,1);
  for(int i=1;i<n;i++){
    int s,t;
    LL w;
    scanf("%d%d%lld",&s,&t,&w);
    G.v[s].pb(t);
    G.v[t].pb(s);
    v[s].emplace_back(t,w);
    v[t].emplace_back(s,w);
  }
  _dfs(1,0,1e15);
  int q;

  G.dfs(1,0);
  for(scanf("%d",&q);q;q--){
    int x,y;scanf("%d",&x);
    vector<int>Query;
    for(int i=1;i<=x;i++) {
      scanf("%d",&y), Query.pb(y), vis[y] = 1;
//      cout<<"in "<<y<<' '<<vis[y]<<endl;
    }
    G.in(Query);
    printf("%lld\n",dfs(1,0));
    for(auto k:Query)vis[k]=0;
    cl(1);
  }
  return 0;
}

CF613D - Kingdom and its Cities

把虚树建出来直接dp。。。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = (1<<19)+5;
#define fi first
#define se second
#define pb push_back
#define wzh(x) cerr<<#x<<'='<<x<<endl;
struct Virtual_tree {
  vector<int>v[N], g[N];
  int dep[N], f[N][22], st[N], top;
  int n, r, dfn[N], CNT;
  void init(int _n, int _r) {
    n = _n; r = _r; CNT = 0;
    for (int i = 1; i <= n; i++) {
      v[i].clear();
      g[i].clear();
    }
  }
  int lca(int u, int v) {
    if (dep[u] < dep[v])swap(u, v);
    for (int i = 19; i >= 0; i--)if ((dep[u] - dep[v]) >> i & 1)u = f[u][i];
    if (u == v)return u;
    for (int i = 19; i >= 0; i--)if (f[u][i] != f[v][i])u = f[u][i], v = f[v][i];
    return f[u][0];
  }
  void dfs(int x, int y) {
    f[x][0] = y; dfn[x] = ++CNT;
    dep[x] = dep[y] + 1;
    for (int j = 1; j <= 19; j++) {
      f[x][j] = f[f[x][j - 1]][j - 1];
    }
    for (auto k : v[x])if (k != y) {
        dfs(k, x);
      }
  }
  void ins(int u) {
    if(top==1){st[++top]=u;return;}
    int lc=lca(u,st[top]);
    if(dep[lc]==dep[st[top]]){st[++top]=u;return;}
    while(top>1 && dep[lc]<=dep[st[top-1]]){
      g[st[top-1]].pb(st[top]);
      top--;
    }
    if(lc!=st[top]){
      g[lc].pb(st[top]);
      st[top]=lc;
    }
    st[++top]=u;
  }
  void in(vector<int>&da) {
    sort(da.begin(), da.end(), [&](int x, int y) {return dfn[x] < dfn[y];});
    top = 0; st[++top] = r;
    for (auto k : da) {
      if(k!=1)ins(k);
    }
    while (top > 1) {
      g[st[top - 1]].pb(st[top]);
      top--;
    }
  }
  int dis(int x,int y){
    return dep[x]+dep[y]-2*dep[lca(x,y)];
  }
} G;
int n,_q,vis[N],st=0,ans=0;
int sz[N];
void dfs(int x,int y){
  sz[x]=vis[x];
  for(auto k:G.g[x]){
    if(k!=y){
      dfs(k,x);
      if(vis[x]&&vis[k]&&G.dis(x,k)==1)st=1;
      if(vis[x]){
        if(sz[k]){
          sz[k]=0;
          ans++;
        }
      }else{
        sz[x]+=sz[k];
      }
    }
  }
  if(sz[x]>1)sz[x]=0,ans++;
  G.g[x].clear();

}
int main() {
  ios::sync_with_stdio(false);
  cin>>n;G.init(n,1);
  for(int i=1;i<n;i++){
    int s,t;
    cin>>s>>t;
    G.v[s].pb(t);
    G.v[t].pb(s);
  }
  G.dfs(1,0);
  for(cin>>_q;_q;_q--){
    int x;cin>>x;
    vector<int>Query;st=0;
    for(int i=1;i<=x;i++){
      int y;
      cin>>y;
      vis[y]=1;
      Query.pb(y);
    }
    G.in(Query);
    ans=0;
    dfs(1,0);
    for(auto k:Query)vis[k]=0;
    if(st)cout<<"-1\n";
    else{
      cout<<ans<<'\n';
    }
  }
  return 0;
}

2020牛客暑期多校训练营(第一场)B Infinite Tree

因为树很大,所以不能把整个树求出来,意味着我们不知道每个点的具体dfs序,但是,显然由题可知相邻两个阶乘的dfs序必然是递增的,而且,树的第一层必然是1,第二层必然都是质数,从第一层到任意一个点的链上由上到下的质数必然是递减的。
那么我们先处理出每个阶乘节点的深度,深度显然是所有质因子幂次之和。就可以直接枚举每个阶乘。算两个相邻阶乘的lca的深度,相邻阶乘 i !     ( i + 1 ) ! i! ~~~(i+1)! i!   (i+1)!的lca的深度就是(i+1)的maxdiv-n的质因子出现的次数。知道了这些东西之后就可以直接建虚树了,建完之后,先求出u=1的答案,然后往儿子上转移,如果转移能减小答案就转。记录最小值即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e5 + 10;
#define fi first
#define se second
#define pb push_back
#define wzh(x) cerr<<#x<<'='<<x<<endl;
int mindiv[N];
void init(){
  mindiv[1]=1;
  for(int i=2;i<N;i++){
    if(!mindiv[i])mindiv[i]=i;
    for(int j=i;j<N;j+=i){
      if(!mindiv[j])mindiv[j]=i;
    }
  }
}
struct Virtual_tree {
  vector<int>v[N], g[N];
  int dep[N],  st[N], top,lc[N];
  int n, r,_t[N],tot;
  void _ad(int x,int y){
    for(;x<=n;x+=x&-x)_t[x]+=y;
  }
  int _su(int x){
    int y=0;
    for(;x;x-=x&-x)y+=_t[x];
    return y;
  }
  void init(int _n, int _r) {
    n = _n; r = _r;tot=n;top=0;
    for (int i = 1; i <= n; i++) {
      v[i].clear();
      _t[i]=0;
    }
    dep[1]=lc[1]=1;
    for(int i=2;i<=n;i++){
      dep[i]=dep[i-1];
      int j;
      for(j=i;j!=mindiv[j];j/=mindiv[j]){
        ++dep[i];
      }
      lc[i]=_su(n)-_su(j-1)+1;//相邻节点lca的深度d
      for(j=i;j!=1;j/=mindiv[j]){
        _ad(mindiv[j],1);
      }
      dep[i]++;
    }
  }
  void ins(int u) {
    if (top == 1) {st[++top] = u; return ;}
    if (lc[u] == dep[st[top]]) {st[++top] = u; return;}
    while (top > 1 && lc[u] <= dep[st[top - 1]]) {
      g[st[top - 1]].pb(st[top]);
      top--;
    }
    if (lc[u] != st[top]) {
      dep[++tot]=lc[u];
      g[tot].pb(st[top]);
      st[top] = tot;
    }
    st[++top] = u;
  }
  void in() {
    top = 0; st[++top] = r;
    for (int i=2;i<=n;i++) {
      ins(i);
    }
    while (top > 1) {
      g[st[top - 1]].pb(st[top]);
      top--;
    }
  }
} G;
int n;
LL w[N],_su[N],ans;
void dfs(int x,int y){
  _su[x]=w[x];
  ans+=(G.dep[x]-G.dep[1])*w[x];
  for(auto k:G.g[x]){
    if(y!=k) {
      dfs(k, x);
      _su[x]+=_su[k];
    }
  }
}
void _dfs(int x,int y,LL tmp){
  ans=min(ans,tmp);
  for(auto k:G.g[x]){
    if(k!=y){
      if(_su[1]-_su[k]-_su[k]<0){
        _dfs(k,x,tmp+1ll*(_su[1]-_su[k]-_su[k])*(G.dep[k]-G.dep[x]));
      }
    }
  }
}
int main() {
  ios::sync_with_stdio(false);
  init();
  while(cin>>n){
    G.init(n,1);ans=0;
    for(int i=1;i<=n;i++)cin>>w[i];
    G.in();
    dfs(1,0);
    _dfs(1,0,ans);
    cout<<ans<<'\n';
    for(int i=1;i<=G.tot;i++){
      w[i]=_su[i]=0;
      G.g[i].clear();
    }
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值