zoukankan      html  css  js  c++  java
  • luoguP1600 天天爱跑步(NOIP2016)(主席树+树链剖分)

    阅读体验:

    https://zybuluo.com/Junlier/note/1303550

    为什么这一篇的Markdown炸了?

    # 天天爱跑步题解(Noip2016)(桶+树上差分 ^ 树剖+主席树)标签:比较综合的题?

    综合整理自:[洛谷P1600 天天爱跑步 题解](https://www.luogu.org/problemnew/solution/P1600)
    提供两种方法,因为网上讲的很详细,~~我也就不需要再多说了~~
    因为自己的代码不是完全按下面任意一种方法来的(有不同的地方)
    ~~所以代码也是复制的,不要介意。。。~~
    可能以后再写一遍的时候放上自己的代码吧(或者那天心血来潮把自己**更复杂**的方法讲一遍)

    ## 桶&树上差分
    ### $25$分算法
    直接暴力,跟着人走,当发现当前节点满足条件就统计
    ### $S$为根节点部分分
    $S$为根节点,如果节点$i$可以观测到人,那么首先要满足`w[i]==deep[i]`,然后以i为根节点的子树包含多少个终点,$i$节点的答案就是几
    ### $T$为根节点
    对于i节点,深度为`deep[i]+w[i]`的起点才会产生贡献。那就$dfs$树上统计呗:
    > 1. 开一个桶`bac[x]`表示当前深度为$x$的起点有多少个
    > 2. 对于节点$i$,访问时先记录当时的`bac[deep[i]+w[i]]`,再往下递归,递归完后检查`bac[deep[i]+w[i]]`,增加了$v$就说明它的子树有$v$个这样的起点,$i$的答案就是$v$
    ### 退化为链部分分

    退化为链你能想到什么?
    所有的路径要么左走要么右走
    我们只考虑右走【左走类似】
    右走时,对于节点$i$,只有节点`i-w[i]`为起点时才会产生贡献。
    那就向右扫一遍:

    > 1. 同样开一个桶`bac[x]`表示扫到现在以$x$为起点的还未走完的路径有多少个
    > 2. 记录当前点$i$的答案`bac[i-w[i]]`
    > 3. 对于在该点结束的路径的`bac[S]--`
    ### 满分算法

    满分算法其实就是综上所述。。
    先把树进行$lca$,路径分为向上和向下走
    > 1. 对于向上走的路径,在i节点,当deep[i]+w[i]==deep[S]时才会产生贡献
    借用以T为根节点的思想,开一个桶来差分统计就好了
    > 2. 对于向下走的路径,在i节点,当deep[i]-w[i]==deep[T]-dis[S,T]-pret[T]时才会产生贡献
    (dis表示路径长,pret表示若该路径起点为lca,则走到lca时是什么时刻,若该路径起点为自然起点,则xpret=0)
    > 3. 进行同样的统计,到i节点时把向上路径的起点S_up和向下路径的终点T_up(起点在上面的终点)的对应的bac[ ]++
    (例如T_up就是bac[deep[T]-dis[S,T]-pret[T]]++),在访问结束时将向下路径的起点S_down和向上路径的终点T_up对应的另一个端点的统计撤销(类似于链状部分分的算法,看不明白可以参照一下)
    > 4. 若该点为lca且该点产生了贡献,贡献值应该-1,因为统计了两次

    总的来说要注意的地方还是很多的,细节处理要特别注意

    ```
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<vector>
    #include<algorithm>
    #define LL long long int
    using namespace std;
    const int maxn=700005,maxm=2000100,INF=2000000000,P=1000000007;
    //快速读入
    inline int read(){
    int out=0,flag=1;char c=getchar();
    while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
    while(c>=48&&c<=57){out=out*10+c-48;c=getchar();}
    return out*flag;
    }
    //边信息建立
    int head[maxn],nedge=0;
    struct EDGE{
    int to,next;
    }edge[maxm];
    inline void build(int a,int b){
    edge[nedge]=(EDGE){b,head[a]};
    head[a]=nedge++;
    edge[nedge]=(EDGE){a,head[b]};
    head[b]=nedge++;
    }
    //lca询问信息建立
    int Head[maxn],nlca=0;
    struct LCA{
    int to,flag,next;
    }Lca[maxm];
    inline void link(int a,int b){
    Lca[nlca]=(LCA){b,0,Head[a]};
    Head[a]=nlca++;
    Lca[nlca]=(LCA){a,0,Head[b]};
    Head[b]=nlca++;
    }
    int N,M,w[maxn],rt=0,Siz[maxn],disrt=INF;
    //数据读入
    void init(){
    fill(head,head+maxn,-1);
    fill(Head,Head+maxn,-1);
    N=read();M=read();
    int a,b;
    for(int i=1;i<N;i++) build(read(),read());
    for(int i=1;i<=N;i++) w[i]=read();
    for(int i=1;i<=M;i++){a=read();b=read();link(a,b);}
    }
    //重心为根
    void dfs1(int u,int f){
    int to,Min=INF,Max=-INF;
    Siz[u]=1;
    for(int k=head[u];k!=-1;k=edge[k].next)
    if((to=edge[k].to)!=f){
    dfs1(to,u);
    Siz[u]+=Siz[to];
    if(Siz[to]<Min) Min=Siz[to];
    if(Siz[to]>Max) Max=Siz[to];
    }
    if(Min==INF) return;
    if(N-Siz[u]<Min&&f) Min=N-Siz[u];
    if(N-Siz[u]>Max) Max=N-Siz[u];
    if(Max-Min<disrt){disrt=Max-Min;rt=u;}
    }
    void focus(){
    dfs1(1,0);
    if(!rt) rt=1;
    //cout<<rt<<endl;
    }
    vector<int> Su[maxn],Sd[maxn],Tu[maxn],Td[maxn];
    int pre[maxn],dep[maxn],dis[maxn],S[maxn],T[maxn],pret[maxn],pathi=0,temp;
    int lca0[maxn];
    bool vis[maxn];
    inline int find(int u){
    return u==pre[u] ? u:pre[u]=find(pre[u]);
    }
    //tarjan_lca算法割路径
    void dfs2(int u,int f){
    int to;
    pre[u]=u;
    dep[u]=dep[f]+1;
    vis[u]=true;
    for(int k=head[u];k!=-1;k=edge[k].next){
    if((to=edge[k].to)!=f){
    dfs2(to,u);
    pre[to]=u;
    }
    }
    for(int k=Head[u];k!=-1;k=Lca[k].next){
    if(!Lca[k].flag&&vis[to=Lca[k].to]){
    Lca[k].flag=Lca[k^1].flag=true;
    int flag=0,m=find(to);
    if(!(k&1)) {flag=1;to^=u^=to^=u;}
    pathi++;
    if(to==m){
    S[pathi]=to;T[pathi]=u;dis[pathi]=dep[u]-dep[to];
    pret[pathi]=0;
    Sd[to].push_back(pathi);Tu[u].push_back(pathi);
    }else if(u==m){
    S[pathi]=to;T[pathi]=u;dis[pathi]=dep[to]-dep[u];
    Td[u].push_back(pathi);Su[to].push_back(pathi);
    }else{
    lca0[pathi]=m;
    S[pathi]=to;T[pathi]=m;dis[pathi]=dep[to]-dep[m];
    Su[to].push_back(pathi);Td[m].push_back(pathi);
    S[++pathi]=m;T[pathi]=u;dis[pathi]=dep[u]-dep[m];
    pret[pathi]=dep[to]-dep[m];
    Sd[m].push_back(pathi);Tu[u].push_back(pathi);
    }
    if(flag) u=to;
    }
    }
    }
    void tarjan_lca(){
    dfs2(rt,0);
    /*for(int i=1;i<=pathi;i++){
    printf("%d %d %d ",S[i],T[i],dis[i]);
    }*/
    }
    int cntS[maxm],cntT[maxm],ans[maxn];
    //树上统计
    void dfs(int u,int f){
    int dS=dep[u]+w[u]+maxn,oriS=cntS[dS],dT=dep[u]-w[u]+maxn,oriT=cntT[dT],to;
    for(unsigned i=0;i<Su[u].size();i++){
    cntS[dep[S[Su[u][i]]]+maxn]++;
    }
    for(unsigned i=0;i<Tu[u].size();i++){
    cntT[dep[T[Tu[u][i]]]-dis[Tu[u][i]]-pret[Tu[u][i]]+maxn]++;
    }
    for(int k=head[u];k!=-1;k=edge[k].next){
    if((to=edge[k].to)!=f){
    dfs(to,u);
    }
    }
    ans[u]=cntS[dS]-oriS+cntT[dT]-oriT;
    for(unsigned i=0;i<Td[u].size();i++){
    cntS[dep[S[Td[u][i]]]+maxn]--;
    //if(u==2) cout<<"lca:"<<lca0[Td[u][i]]<<endl;
    if(lca0[Td[u][i]]==u&&dep[S[Td[u][i]]]+maxn==dS) ans[u]--;
    }
    for(unsigned i=0;i<Sd[u].size();i++){
    cntT[dep[T[Sd[u][i]]]-dis[Sd[u][i]]-pret[Sd[u][i]]+maxn]--;
    }
    }
    //打印答案
    void print(){
    printf("%d",ans[1]);
    for(int i=2;i<=N;i++) printf(" %d",ans[i]);
    printf(" ");
    }
    int main(){
    init();
    focus();
    tarjan_lca();
    dfs(rt,0);
    print();
    return 0;
    }
    ```

    ## 树剖&主席树(哇,大佬Tyher)
    下面那些式子就不推到了,看了上面的题解你就会发现其实桶的做法是比较难维护的因为树上差分这种东西毕竟思维难度还是比较高
    因为桶做法的本质还是在$dfs$过程中到$i$节点,看$i$节点的子树中的桶有多少个起点满足要求,终点满足要求
    然后在考虑$lca$取不取,去重复之类的巴拉巴拉
    所以智商不够数据结构来凑
    因为桶做法之所以麻烦就是因为在计算当前节点时会把其他子树中深度相同的点也算进来
    为了方便,开$n+1$颗线段树,每颗线段树代表某一深度的点的集合,动态开点
    然后考虑某一路径上的点能对哪一个深度的点产生影响
    对于$s$到$lca$的路径,上面已经说的很清楚了,即$s$的深度的点
    那么就在`root[de[s]]`这个线段树上,从$lca$到s的路径上所经过的点$+1$
    这个路径$+1$的处理是可以树剖搞定的,$lca$也可以用树剖顺便求了
    然后是$lca$到$t$的路径上,也是一样的道理,注意右移$Max$位
    注意最后在`de[s]`线段树中单点减去$lca$的贡献,会重复一次的。
    修改应当是区间修改,最后查询的时候一定要记得下放$lazy$
    为了方便你可以开两颗主席树,分开考虑$s$到$lca$和$lca$到$t$
    但是$val$,$ls$,$rs$几个数组可以共用
    可以在树剖的时候就把改开的点全部开了
    最后附上代码,看不懂可以私信
    ```
    #include<iostream>
    #include<cstdlib>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<vector>
    #include<queue>
    #define il inline
    #define rg register
    #define ll long long
    #define ld long double
    #define N 300000
    #define inf 2147483647
    using namespace std;
    int n,m,u,v,cnt,num;
    int hd[N],w[N],s[N],t[N];
    int de[N],top[N],son[N],tot[N];
    int fa[N],idx[N];
    struct Edge{
    int nt,to;
    }edge[N<<1];
    int rt1[N*2],rt2[N*2],ls[N*40],rs[N*40],val[N*40];
    il void re(rg int &x);
    il void link(rg int fm,rg int to);
    void Dfs1(rg int i,rg int fm);
    void Dfs2(rg int i,rg int head);
    int lca(rg int x,rg int y);
    il void work(rg int s,rg int t);
    void add(rg int now,rg int le,rg int ri,rg int pos);
    void update(rg int now,rg int le,rg int ri,rg int L,rg int R,rg int w);
    int query(rg int now,rg int le,rg int ri,rg int pos);
    int main(){
    freopen("s.in","r",stdin);
    re(n),re(m);
    for(rg int i=1;i<n;++i){
    re(u),re(v);
    link(u,v),link(v,u);
    }
    for(rg int i=1;i<=n;++i)
    re(w[i]);
    for(rg int i=1;i<=m;++i)
    re(s[i]),re(t[i]);
    cnt=0,Dfs1(1,0),Dfs2(1,1);
    for(rg int i=1;i<=m;++i)
    work(s[i],t[i]);
    for(rg int i=1;i<=n;++i){
    int ans1=query(rt1[de[i]+w[i]],1,n,idx[i]);
    int ans2=query(rt2[w[i]-de[i]+N],1,n,idx[i]);
    printf("%d ",ans1+ans2);
    }
    return 0;
    }
    int query(rg int now,rg int le,rg int ri,rg int pos){
    if(!now)return 0;
    if(le==ri)return val[now];
    if(val[now]){
    if(ls[now])val[ls[now]]+=val[now];
    if(rs[now])val[rs[now]]+=val[now];
    val[now]=0;
    }
    rg int mid=((le+ri)>>1);
    if(pos<=mid)return query(ls[now],le,mid,pos);
    else return query(rs[now],mid+1,ri,pos);
    }
    il void work(rg int s,rg int t){
    rg int Lca=lca(s,t),u=s;
    while(top[u]!=top[Lca]){
    update(rt1[de[s]],1,n,idx[top[u]],idx[u],1);
    u=fa[top[u]];
    }
    update(rt1[de[s]],1,n,idx[Lca],idx[u],1);
    u=t;
    while(top[u]!=top[Lca]){
    update(rt2[de[s]-2*de[Lca]+N],1,n,idx[top[u]],idx[u],1);
    u=fa[top[u]];
    }
    update(rt2[de[s]-2*de[Lca]+N],1,n,idx[Lca],idx[u],1);
    update(rt1[de[s]],1,n,idx[Lca],idx[Lca],-1);
    }
    void add(rg int now,rg int le,rg int ri,rg int pos){
    if(le==ri)return;
    rg int mid=((le+ri)>>1);
    if(pos<=mid){
    if(!ls[now])ls[now]=(++num);
    add(ls[now],le,mid,pos);
    }
    else{
    if(!rs[now])rs[now]=(++num);
    add(rs[now],mid+1,ri,pos);
    }
    }
    void update(rg int now,rg int le,rg int ri,rg int L,rg int R,rg int w){
    if(!now)return;
    if(L==le&&R==ri){val[now]+=w;return;}
    rg int mid=((le+ri)>>1);
    if(R<=mid)update(ls[now],le,mid,L,R,w);
    else if(L>mid)update(rs[now],mid+1,ri,L,R,w);
    else update(ls[now],le,mid,L,mid,w),update(rs[now],mid+1,ri,mid+1,R,w);
    }
    int lca(rg int x,rg int y){
    while(top[x]!=top[y]){
    if(de[top[x]]<de[top[y]])swap(x,y);
    x=fa[top[x]];
    }
    if(de[x]>de[y])return y;
    else return x;
    }
    void Dfs1(rg int i,rg int fm){
    de[i]=de[fm]+1,fa[i]=fm;
    tot[i]=1;
    rg int maxn=0;
    for(rg int k=hd[i];k;k=edge[k].nt){
    rg int qw=edge[k].to;
    if(qw==fm)continue;
    Dfs1(qw,i),tot[i]+=tot[qw];
    if(tot[qw]>maxn)maxn=tot[qw],son[i]=qw;
    }
    }
    void Dfs2(rg int i,rg int head){
    idx[i]=(++cnt),top[i]=head;
    if(!rt1[de[i]+w[i]])rt1[de[i]+w[i]]=(++num);
    add(rt1[de[i]+w[i]],1,n,idx[i]);
    if(!rt2[w[i]-de[i]+N])rt2[w[i]-de[i]+N]=(++num);
    add(rt2[w[i]-de[i]+N],1,n,idx[i]);
    if(!son[i])return;
    Dfs2(son[i],head);
    for(rg int k=hd[i];k;k=edge[k].nt)
    if(!idx[edge[k].to])
    Dfs2(edge[k].to,edge[k].to);
    }
    il void re(rg int &x){
    rg int res=0;rg int w=1;char c=getchar();
    while((c<'0'||c>'9')&&c!='-')c=getchar();
    if(c=='-')w=-1,c=getchar();
    while(c>='0'&&c<='9')res=(res<<3)+(res<<1)+c-'0',c=getchar();
    x=w*res;
    }
    il void link(rg int fm,rg int to){
    edge[++cnt].nt=hd[fm];
    edge[cnt].to=to;
    hd[fm]=cnt;
    }
    ```

  • 相关阅读:
    YbtOJ20030 连珠风暴
    YbtOJ20029 最大权值
    P6859 蝴蝶与花
    P4115 Qtree4
    P2486 [SDOI2011]染色
    P2487 [SDOI2011]拦截导弹
    P5163 WD与地图
    P3733 [HAOI2017]八纵八横
    CF1100F Ivan and Burgers
    P1712 [NOI2016]区间
  • 原文地址:https://www.cnblogs.com/cjoierljl/p/9107682.html
Copyright © 2011-2022 走看看