懒癌晚期,迟到许久的主席树(可持久化线段树)

就是将一颗权值线段树的中间变化过程保存下来

朴素的想法就是保存中间每颗树(但这样空间会炸),但仔细想想插入的过程发现每次只有一条长为logn的链改变了,所以我们只需要保存那个链并将其连在原树上即可这样就保存了中间所有变化过程的树(合理利用公共资源)节点的编号也不是之前二叉树编号规则了,这些地方就很像动态开点线段树(很有意思的一个东西,可以开拓思路用)

此处将我学习的链接贴一下:https://www.bilibili.com/video/BV1ib41137Ry?from=search&seid=14598533944342614266

还有主席树的图文讲解,可以自己搜博客看看,理解了思想再看代码,并多想想为什么这么写

bibi上的UP主讲的(哔哩哔哩上很多学习资源的,自己多学学呀)

最早的板子题就不放了,(因为我膨胀了)

2020多校牛客第五场H-Interval

 

定义个这 

n个数q次询问每次给你个L和R求  set S(l,r)大小,(会去重的)

每次答案与上次答案有关(所以别想莫队了)

考虑固定左端点,最多会得到logn个右端点,有这些区间ans就+1

这就是典型的二维偏序问题,L按秩排序,把L相同的放在主席树同一区间段记录每个L的有边界,然后L到R中小于R的区间段数就是小于R的区间求和就完事了(题解压根就没写这句,这就是著名的众所周知的知识)

但是这没法去重,所以考虑值相同的L1,R1,  L2,R2  再加一个L1,R2的权值为-1的区间段然后再二维偏序就完事了

是不是好简单,代码也没写很久,也没改很久就出来了,可是赛场上你想的到么

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
typedef long long ll;
#define int ll
int n,q;
vector<int>son[N];
vector<int>wei[30];
vector<pair<int,int>>v[N*30];
ll ppp=pow(2,30)-1;
int a[N];
int rr[N];
struct node
{
    int ls,rs,sum;
} tree[N*200];
int root[N*200];
int cnt;
int cnt__;
void init();
void update(int l,int r,int &x,int y,int pos,int k)///建树,y为前一颗树的根节点,先复制上一个数的节点信息再进行修改
{

    tree[++cnt]=tree[y];
    tree[cnt].sum+=k;
    x=cnt;
    if(l==r)
        return;
    int mid=(l+r)/2;
    if(pos<=mid)
        update(l,mid,tree[x].ls,tree[y].ls,pos,k);
    else
        update(mid+1,r,tree[x].rs,tree[y].rs,pos,k);
}
int query(int l,int r,int x,int y,int k)///前缀和思想
{
    if(l==r)
        return tree[y].sum-tree[x].sum;
    int mid=(l+r)/2;
    if(k<=mid)
        return query(l,mid,tree[x].ls,tree[y].ls,k);
    else
        return tree[tree[y].ls].sum-tree[tree[x].ls].sum+query(mid+1,r,tree[x].rs,tree[y].rs,k);
}

void solve()
{
    scanf("%lld",&q);
    int ans=0;
    while(q--)
    {
        int L,R;
        scanf("%lld%lld",&L,&R);
        L=(L^ans)%n+1;
        R=(R^ans)%n+1;
        if(L>R)swap(L,R);
        ans=query(1,n,root[rr[L-1]],root[rr[R]],R);
        printf("%lld\n",ans);
    }
}
signed main()
{
    init();
    solve();
}
void init()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    for(int i=0;i<30;i++)///枚举二进制数
    {
        for(int j=1;j<=n;j++)
        {
            if((a[j]&(1<<i))==0)
                wei[i].push_back(j);
        }
    }

    for(int i=1;i<=n;i++)
    {
        son[i].push_back(i);
        for(int j=0;j<30;j++)
        {
            int x=lower_bound(wei[j].begin(),wei[j].end(),i)-wei[j].begin();
            if(x==wei[j].size()||i==wei[j][x]) continue;
            son[i].push_back(wei[j][x]);
        }
    }
    for(int i=1;i<=n;i++)
    {
        sort(son[i].begin(),son[i].end());
    }
    map<int,int>mp;
    for(int i=1;i<=n;i++)
    {
        ll cad=ppp;
        for(int j=0;j<son[i].size();j++)
        {
            cad&=a[son[i][j]];
            if(!mp[cad])
            {
                v[++cnt__].push_back(make_pair(i,son[i][j]));
                mp[cad]=cnt__;
            }
            else
                v[mp[cad]].push_back(make_pair(i,son[i][j]));
//                cout<<cad<<'?'<<endl;
        }
    }
    //for(int i=0;i<=10;i++)cout<<i<<':'<<mp[i]<<endl;
    //cout<<"???"<<endl;
    for(int i=1;i<=cnt__;i++)
        sort(v[i].begin(),v[i].end());
    for(int i=1;i<=cnt__;i++)
    {
        for(int j=0;j<v[i].size()-1;j++)
        {
            int LL=v[i][j].first;
            int RR=v[i][j+1].second;
            son[LL].push_back(-RR);
        }
    }
    int d=1;
  for(int i=1;i<=n;i++)
  {
      for(int j=0;j<son[i].size();j++)
      {
          //cout<<i<<' '<<son[i][j]<<endl;
          if(son[i][j]>0)
          update(1,n,root[d],root[d-1],son[i][j],1);
          else
            update(1,n,root[d],root[d-1],-son[i][j],-1);
          d++;
      }
      rr[i]=d-1;
  }
 // cout<<d<<endl;
}

 

 

 

牛客主席树练习题——树的距离

题目链接:https://ac.nowcoder.com/acm/problem/14415

题目描述 

wyf非常喜欢树。一棵有根数树上有N个节点,1号点是他的根,每条边都有一个距离,而wyf是个爱问奇怪问题的熊孩子,他想知道对于某个点x,以x为根的子树上,所有与x距离大于等于k的点与x的距离之和。

输入描述:

第一行一个正整数N

接下来N-1描述这棵树,每行两个数第i行两个数p和D表示树上有一条p到i+1长度为D的边。(p<=i)

下面一行一个正整数Q表示wyf的询问次数。

接下来Q行每行两个正整数x和k。 (1<=N,Q<=2x105,1<=D,K<=106)

输出描述:

对于每次询问x,k输出以x为根的子树上,所有与x距离大于等于k的点与x的距离之和。(若不存在这样的点,则输出应为0)

按树上dfs序建主席树,先儿子们后父亲,x子树为root[x]-root[zuo[x]-1],主席树上维护离根节点距离,区间和,离x大于等于k=离1大于等于dep[x]+k,然后搜个区间和就完事了(说是这么说,然而我3个小时了还没撸出代码)

留份错误的代码吧(有机会一定写)如同去年的网络流那样,今年才有时间去写

我A了   找到了bug后  过了95%的数据,然后对拍,发现边界处理有些问题,然后过了

虽然它只是个简单主席树板子题,虽然看起来只是一小步,却是我学习主席树算法道路上的一大步

#include<bits/stdc++.h>
using namespace std;
const double pi = acos(-1.0);
typedef long long ll;
const ll mod =998244353;
const ll N=2e5+7;
#define FOPEN freopen("C:\\Users\\l\\P3381_8.in","r",stdin)
void init();
ll n,m,cnt,a[N],root[N],k;
ll t;
ll NO[N];
struct kkk
{
    ll to,w;
};
struct node
{
    ll ls,rs,num,sum;
} tree[N*50];
void update(ll l,ll r,ll &x,ll y,ll pos);///建树,y为前一颗树的根节点,先复制上一个数的节点信息再进行修改
ll query(ll l,ll r,ll x,ll y,ll k,ll xx);///前缀和思想
ll zuo[N];
vector<kkk>son[N];
vector<ll>v,xu;
ll dep[N];
void dfs(ll x,ll fa)
{
    if(son[x].size()==1) zuo[x]=x;
    for(ll i=0;i<son[x].size();i++)
    {
        auto d=son[x][i];
        if(d.to==fa) continue;
        dep[d.to]=dep[x]+d.w;
        dfs(d.to,x);
    }
    if(!zuo[fa]) zuo[fa]=zuo[x];
    v.push_back(dep[x]);
    NO[x]=xu.size();
    xu.push_back(x);
}
ll getid(ll x)///用于离散化
{
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
void solve()
{
   ll q;scanf("%lld",&q);
   while(q--)
   {
       ll x,k;
       scanf("%lld%lld",&x,&k);
       k+=dep[x];
       if(k>v[v.size()-1]) cout<<0<<endl;
       else
       cout<<query(1,n,root[NO[zuo[x]]-1],root[NO[x]],getid(k),x)<<endl;
   }

}
signed main()
{
    init();
    //for(ll i=1;i<=n;i++) cout<<i<<':'<<root[i]<<endl;
    solve();
}
void init()
{
    scanf("%lld",&n);
    for(ll i=2;i<=n;i++)
    {
        ll to,w;
        scanf("%lld%lld",&to,&w);
        son[i].push_back({to,w});
        son[to].push_back({i,w});
    }
    son[1].push_back({0,0}); xu.push_back(0);
    dfs(1,0);
    //for(int i=1;i<=n;i++) cout<<zuo[i]<<' ';cout<<endl;
    //for(int i=1;i<=n;i++) cout<<NO[i]<<' ';cout<<endl;
    //for(int i=1;i<=n;i++) cout<<xu[i]<<' ';cout<<endl;
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());

    for(ll i=1;i<=n;i++)
    {
        update(1,n,root[i],root[i-1],getid(dep[xu[i]]));
    }
}
void update(ll l,ll r,ll &x,ll y,ll pos)///建树,y为前一颗树的根节点,先复制上一个数的节点信息再进行修改
{

    tree[++cnt]=tree[y];
    tree[cnt].sum+=v[pos-1];
    tree[cnt].num++;
    x=cnt;
    if(l==r)
        return;
    ll mid=(l+r)/2;
    if(pos<=mid)
        update(l,mid,tree[x].ls,tree[y].ls,pos);
    else
        update(mid+1,r,tree[x].rs,tree[y].rs,pos);
}
ll query(ll l,ll r,ll x,ll y,ll k,ll xx)///前缀和思想
{
    ll ans=0;
    if(l==r)
        return tree[y].sum-tree[x].sum-(tree[y].num-tree[x].num)*dep[xx];
    ll mid=(l+r)/2;
    if(k<=mid)
        ans+=query(l,mid,tree[x].ls,tree[y].ls,k,xx),ans+=tree[tree[y].rs].sum-tree[tree[x].rs].sum-(tree[tree[y].rs].num-tree[tree[x].rs].num)*dep[xx];
    else
        ans+=query(mid+1,r,tree[x].rs,tree[y].rs,k,xx);
        return ans;
}

/*
5
1 76
2 75
2 67
4 59
5
4 67
2 62
4 89
6 37
3 42


*/

 

 

/

更新,主席树求区间mex    权值主席树(啊哈,本来理解的就是对的,传参传错了)

按时间戳每个叶子节点存放该点出现的最新事件,维护区间最小值,二分1-n查找R节点树  如果该区间最小值小于L 说明该区间有值没在L-R中出现,它就是mex

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+7;
typedef long long ll;
//#define int ll
int a[N];
struct node
{
    int ls,rs,mi;
} tree[N*200];
int root[N];
int cnt;
void init();
void update(int l,int r,int &x,int y,int pos,int k)///建树,y为前一颗树的根节点,先复制上一个数的节点信息再进行修改
{

    tree[++cnt]=tree[y];
    //tree[cnt].mi=max(tree[cnt].mi,k);
    x=cnt;
    if(l==r)
    {
        tree[cnt].mi=k;
        return;
    }
    int mid=(l+r)/2;
    if(pos<=mid)
        update(l,mid,tree[x].ls,tree[y].ls,pos,k);
    else
        update(mid+1,r,tree[x].rs,tree[y].rs,pos,k);
    tree[x].mi=min(tree[tree[x].ls].mi,tree[tree[x].rs].mi);
}
int query(int l,int r,int x,int k)///前缀和思想
{
    int mid=(l+r)/2;
    if(l==r) return l;
    if(tree[tree[x].ls].mi<k)
        return query(l,mid,tree[x].ls,k);
    else
        return query(mid+1,r,tree[x].rs,k);
}
void dfs(int l,int r,int x)
{

    cout<<l<<' '<<r<<' '<<tree[x].mi<<endl;
    if(l==r) return;
    int mid=(l+r)/2;
    dfs(l,mid,tree[x].ls);

    dfs(mid+1,r,tree[x].rs);
}
void solve()
{
    
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int x;cin>>x;
        update(0,n,root[i],root[i-1],x,i);
        //dfs(0,n,root[i]);
    }
    while(m--)
    {
        int a1,a2;
        cin>>a1>>a2;
        cout<<query(0,n,root[a2],a1)<<endl;
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    init();
    solve();
}
void init()
{

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值