EOJ 306 树上问题

 

 

题解:

因为w大于1,所以,题意就是,有多少(x,z),存在x到z的路径上,有一个x<y<z的y

w没用的其实。

树上路径问题,有什么方法吗?

1.树链剖分。这个主要方便处理修改操作。

2.点分治,对于静态无修改点树上统计,非常好用。

3.一些其他的:

利用lca,dfs序,判断点在路径上,点在子树里一些情况。

倍增,处理fa[N][20],dis[N][20] ,

二分再套一个倍增?

4.还有一些灵活应变的:

例如:拆路径为x到lca,lca到y,可以在x,y记录一些lca的信息,把路径就变成了点。

例题:牛客网NOIP赛前集训营-提高组(第一场)T3

 

这个题,静态无修树上统计,就点分治了。

还可以再带一个log

那么当前层的重心G,统计过G路径。

树形背包思想,直接统计z能和之前的那些x凑成点对,记录x到G路径上的大于x最小的编号nx(因为是存在,不是任意嘛)

然后记录z到G路径上小于z的最大编号pz

如果pz>x,那么可以

如果nx<y,那么可以

但是pz<nx的情况被算重了。去重要用二维数据结构两个log就TLE了。

 

正难则反。考虑所有的点对。C(n,2)

对于x到z路径上都比x,z小的去掉,都比x、z大的去掉。就可以了。

具体来说,维护一个树状数组,

以去掉路径上都比x、z小的为例:

之前访问的作为x,如果x到根节点的路径上(包括根)最大值(不存在就是一个任意问题了)小于x,把x位置++

dfs统计,对于z,如果G到z路径上的最大值mx小于z,统计query(z-1)-query(mx)

表示得到编号在mx+1到z-1的x,且x到根路径上的最大值小于x的x数量。

就可以去掉这部分。

当然,因为G儿子的循环顺序,必须正序循环一遍,再倒序循环一遍。当前都作为z,之前的作为x,一定不会漏

 

另一个都比x,z大的同理。

而且之后统计路径上比x、z都大的情况不会算重。

 

小细节:

1.C(n,2)会爆int

2.子树的sz不是开始统计的sz,递归之前,必须从新的根即重心G再dfs统计sz

3.点分治一定要时刻控制:if(vis[e[i].to]) continue 否则T得飞起,WA的痛快。

4.发现,对于每条边的两端点对,会被减掉两次。

所以,ans开始还要加上(n-1)

 

代码:

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100000+5;
const int inf=0x3f3f3f3f;
ll n;
int rt,nowsz;
bool vis[N];
int mxsz[N],sz[N];
int f[N];
void add(int x,int c){//树状数组 
    for(;x<=n;x+=x&(-x)) f[x]+=c;
}
int query(int x){
    int ret=0;for(;x;x-=x&(-x)) ret+=f[x];return ret;
}
int sta[N],top;
int mxid[N],miid[N];//路径上编号最小值,最大值 
ll ans;
struct node{
    int nxt,to;
    int pre;
}e[2*N];
int hd[N],cnt;
int las[N];
void con(int x,int y){//注意建立双向邻接表,便于反过来dfs 
    if(hd[x]&&e[hd[x]].nxt==0) las[x]=hd[x];
    e[++cnt].nxt=hd[x];
    e[hd[x]].pre=cnt;
    e[cnt].to=y;
    hd[x]=cnt;
}
void dfs0(int x,int fa){//dfs0找根 
    sta[++top]=x;
    mxid[x]=0;mxsz[x]=0;
    miid[x]=0;
    sz[x]=1;
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        if(vis[y]) continue;
        dfs0(y,x);
        sz[x]+=sz[y];
        mxsz[x]=max(mxsz[x],sz[y]);
    }
    if(mxsz[x]<=nowsz/2&&(nowsz-sz[x])<=nowsz/2) rt=x;
}
void fsz(int x,int fa){//找完rt更新sz 
    sz[x]=1;
    for(int i=hd[x];i;i=e[i].nxt){
        if(vis[e[i].to]) continue; 
        if(e[i].to!=fa){
            fsz(e[i].to,x);
            sz[x]+=sz[e[i].to];
        }
    }
}
void dfs1(int x,int mx,int fa){//dfs1统计答案,对于路径上的点都比x,z小的。 
    mxid[x]=mx;
    if(mx<x) ans-=(query(x-1)-query(mx));
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        if(vis[y]) continue;
        dfs1(y,max(mx,x),x);
    }
}
void upda1(int x,int fa){//dfs1之后,更新子树 
    if(mxid[x]<x) add(x,1);
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        if(vis[y]) continue;
        upda1(y,x);
    }
}
void srt1(int x,int fa,int mx){//根比较麻烦,单独处理 
    //if(mx<rt&&x>rt) ans--;
    if(mx<rt&&x>mx) ans--;
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        if(vis[y]) continue;
        srt1(y,x,max(mx,x));
    }
}
void dvi1(int in){//点分治1 
    dfs0(in,0);
    fsz(rt,0);
    for(int i=hd[rt];i;i=e[i].nxt){
        if(vis[e[i].to]) continue;
        dfs1(e[i].to,rt,rt);
        upda1(e[i].to,rt);
    }
    for(int i=1;i<=top;i++){
        int x=sta[i];
        if(x==rt) continue;
        if(mxid[x]<x) add(x,-1);
    }
    for(int i=las[rt];i;i=e[i].pre){//反向再处理一次 
        if(vis[e[i].to]) continue;
        dfs1(e[i].to,rt,rt);
        upda1(e[i].to,rt);
    }
    for(int i=hd[rt];i;i=e[i].nxt){
        if(vis[e[i].to]) continue;
        srt1(e[i].to,rt,0);
    }
    while(top){
        int x=sta[top--];
        if(x==rt) continue;
        if(mxid[x]<x) add(x,-1);
    }
    vis[rt]=1;
    for(int i=hd[rt];i;i=e[i].nxt){
        int y=e[i].to;
        if(vis[y]) continue;
        nowsz=sz[y];
        dvi1(y);
    }
}
//以下是x,z路径上点都比较大的,同理 
void dfs2(int x,int mi,int fa){
    miid[x]=mi;
    if(mi>x) ans-=(query(mi-1)-query(x));
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        if(vis[y]) continue;
        dfs2(y,min(mi,x),x);
    }
}
void upda2(int x,int fa){
    if(miid[x]>x) add(x,1);
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        if(vis[y]) continue;
        upda2(y,x);
    }
}
void srt2(int x,int fa,int mi){
    if(mi>rt&&x<mi) ans--;
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa) continue;
        if(vis[y]) continue;
        srt2(y,x,min(mi,x));
    }
}
void dvi2(int in){
    dfs0(in,0);
    fsz(rt,0);
    for(int i=hd[rt];i;i=e[i].nxt){
        if(vis[e[i].to]) continue;
        dfs2(e[i].to,rt,rt);
        upda2(e[i].to,rt);
    }
    for(int i=1;i<=top;i++){
        int x=sta[i];
        if(x==rt) continue;
        if(miid[x]>x) add(x,-1);
    }
    for(int i=las[rt];i;i=e[i].pre){
        if(vis[e[i].to]) continue;
        dfs2(e[i].to,rt,rt);
        upda2(e[i].to,rt);
    }
    for(int i=hd[rt];i;i=e[i].nxt){
        if(vis[e[i].to]) continue;
        srt2(e[i].to,rt,inf);
    }
    while(top){
        int x=sta[top--];
        if(x==rt) continue;
        if(miid[x]>x) add(x,-1);
    }
    vis[rt]=1;
    for(int i=hd[rt];i;i=e[i].nxt){
        int y=e[i].to;
        if(vis[y]) continue;
        nowsz=sz[y];
        dvi2(y);
    }
}
int main(){
    scanf("%lld",&n);
    int x,y,z;
    for(int i=1;i<=n-1;i++){
        scanf("%d%d%d",&x,&y,&z);
        con(x,y);con(y,x);
    }
    ans=(n-1)*n/2 + (n-1);//warning warning warning!!!
    nowsz=n;
    dvi1(1);
    
    memset(vis,0,sizeof vis);//解开封锁 
    top=0;
    nowsz=n;
    dvi2(1);
    printf("%lld",ans);
    return 0;
}

 

posted @ 2018-09-12 10:59  *Miracle*  阅读(286)  评论(0编辑  收藏  举报