BZOJ 4719: [Noip2016]天天爱跑步 线段树合并

title

BZOJ 4719

LUOGU 1600

简化题意:

小c同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。

这个游戏的地图可以看作一一棵包含 \(n\) 个结点和 \(n-1\) 条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从 \(1\)\(n\) 的连续正整数。

现在有 \(m\) 个玩家,第 \(i\) 个玩家的起点为 \(S_i\) ,终点为 \(T_i\) 。每天打卡任务开始时,所有玩家在第 \(0\) 秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树,所以每个人的路径是唯一的)

小c想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。 在结点 \(j\) 的观察员会选择在第 \(W_j\) 秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第 \(W_j\) 秒也理到达了结点 \(j\) 。 小C想知道每个观察员会观察到多少人?

注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一 段时间后再被观察员观察到。 即对于把结点 \(j\) 作为终点的玩家:若他在第 \(W_j\) 秒前到达终点,则在结点 \(j\) 的观察员不能观察到该玩家;若他正好在第 \(W_j\) 秒到达终点,则在结点 \(j\) 的观察员可以观察到这个玩家。

analysis

对于一个人,他的路程会分为两段,一段向上(根),一段向下,考虑在向上过程中他能产生贡献的观察者具有什么性质:设出发点深度为 \(dep[x]\),观察者深度为 \(dep[y]\),观察的时间为 \(t\),需满足 \(dep[x]−dep[y]=t\),换句话说就是 \(dep[y]+t=dep[x]\) 。向下那一段的推导类似,下面的部分也只以向上的路径为例来解释。

现在我们记录每个点下方出发点深度为 \(dep[x]\) 的人数,需要对于每个出发点更新出发点到出发点与目的地的 \(lca\) 的所有点,想到到开一颗权值线段树,用线段树合并不断把信息往上传,在 \(lca\) 处消除这次更新的影响,这样一来直接我们就可以在树上统计答案了。向下路径的处理与向上路径类似。

code

#include

const int maxn=3e5+10;

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    templateinline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }

    char Out[1<<24],*fe=Out;
    inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
    templateinline void write(T x,char str)
    {
        if (!x) *fe++=48;
        if (x<0) *fe++='-', x=-x;
        T num=0, ch[20];
        while (x) ch[++num]=x%10+48, x/=10;
        while (num) *fe++=ch[num--];
        *fe++=str;
    }
}

using IO::read;
using IO::write;

int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
    ver[++len]=y,Next[len]=head[x],head[x]=len;
}

namespace SGT
{
    struct Orz{int l,r,z;}c[maxn*60];
    int num=0;
    inline void Change(int &x,int l,int r,int k,int z)
    {
        if (!x) x=++num;
        c[x].z+=z;
        if (l==r) return ;
        int mid=(l+r)>>1;
        if (k<=mid) Change(c[x].l,l,mid,k,z);
        else Change(c[x].r,mid+1,r,k,z);
    }

    inline int query(int x,int l,int r,int k)
    {
        if (l==r) return c[x].z;
        int mid=(l+r)>>1;
        if (k<=mid) return query(c[x].l,l,mid,k);
        else return query(c[x].r,mid+1,r,k);
    }

    inline int merge(int x,int y)
    {
        if (!x) return y;
        if (!y) return x;
        c[x].l=merge(c[x].l,c[y].l);
        c[x].r=merge(c[x].r,c[y].r);
        c[x].z=c[x].z+c[y].z;
        return x;
    }
}

using SGT::Change;
using SGT::query;
using SGT::merge;

namespace lca
{
    int dfn[maxn],id;
    int f[maxn][21],dep[maxn];
    inline void dfs(int x)
    {
        dfn[x]=++id;
        for (int i=1; i<=20; ++i) f[x][i]=f[f[x][i-1]][i-1];
        for (int i=head[x]; i; i=Next[i])
        {
            int y=ver[i];
            if (y==f[x][0]) continue;
            f[y][0]=x;
            dep[y]=dep[x]+1;
            dfs(y);
        }
    }

    inline int LCA(int x,int y)
    {
        if (dep[x]>dep[y]) std::swap(x,y);
        for (int i=20; i>=0; --i)
            if (dep[y]-(1<=dep[x]) y=f[y][i];
        if (x==y) return x;
        for (int i=20; i>=0; --i)
            if (f[x][i]^f[y][i]) x=f[x][i],y=f[y][i];
        return f[x][0];
    }
}

using lca::dfs;
using lca::LCA;
using lca::dep;
using lca::f;

int tot,n,m,u[maxn],v[maxn];
int ans[maxn],w[maxn];
inline void get(int x)
{
    for (int i=head[x]; i; i=Next[i])
    {
        int y=ver[i];
        if (y==f[x][0]) continue;
        get(y);
        u[x]=merge(u[x],u[y]), v[x]=merge(v[x],v[y]);
    }
    ans[x]=query(u[x],1,tot,dep[x]+w[x])+query(v[x],1,tot,w[x]-dep[x]+n);
}

int main()
{
    read(n);read(m); tot=n<<1;
    for (int i=1,x,y; i

转载于:https://www.cnblogs.com/G-hsm/p/11426646.html

你可能感兴趣的:(BZOJ 4719: [Noip2016]天天爱跑步 线段树合并)