[LCA]【NOIP2016D1T3】天天爱跑步 题解

传送门:
UOJ
洛谷

解题报告

这道题应该是NOIP近3年来最难的一道题了(2015寻找道路&2014解方程表示不服)

细思极恐……这两年最难的两道题目尽然都要用LCA。所以今年?

树链剖分?LCT?什么东西,能吃吗?


这道题的原数据有一些奇奇怪怪的限制,可能是你考试时骗分的灵感,但也可能是……这道题正解的提示(反正我从来没注意过)。

声明:以下的数据分析不是我这个蒟蒻分析的出来的,copy了ZigZagK的思路。

如果是图是一条链,那么只有在 x+w[x] xw[x] 时才能被观察员发现。
S=1,T=1 说明S和T其实需要分开来处理

怎么分开?可以考虑分成两条直链(没有拐弯) S>LCA LCA>T ,方便处理。
1. S>LCA
对于 S>LCA 来说,如果点x上的观察员可以看到S的话。很明显有 dep[S]dep[x]=w[x] (x应该不能在LCA上方),但是可以转化一下 dep[S]=dep[x]+w[x]
2. LCA>T
LCA>T 的分析也不难,设 S>T 距离为dis( dis=dep[S]+dep[T]dep[LCA]2 ),那么当 dep[T]dep[x]=disw[x] 时点x上的观察员可以观察到,那么同样有 dep[x]w[x]=dep[T]dis

所以,对于一个点x我们只要分别找出有多少个任务满足 dep[S]=dep[x]+w[x] dep[T]dis=dep[x]w[x] 就可以了。注意有可能是负的,所以需要向右推。这里以 s>LCA 为例,ha[k]表示k这个值出现了几次,然后递归处理。对于节点 x ,先递归 x 的子树,如果遇到S就 ha[dep[S]]++ ,返回来遇到S的LCA就 ha[dep[S]] (差分大法好),最后全遍历完后统计 ha[dep[x]+w[x]] ?注意,这个 x 有可能在一些 LCA 的下面不符合我们的要求。我们可以记录lst表示刚遇到x时的hash[dep[x]+w[x]],然后处理完x的儿子的子树后hash[dep[x]+w[x]]-lst就是满足的答案了(容斥大法好)。

但是有没有注意 LCA 会处理两次?解决方法就是判断 LCA 是否有计算,如果有计算那么根据上面的流程一定记了两次。所以需要减1次。

综上,这道题其实没有蕴含什么高级算法,也就一个LCA,但其思维复杂度,足以给那些高级算法和高级数据结构学傻的人敲了一记警钟(对我这种蒟蒻而言就是末日来了,虽然我没有学傻,因为我根本学不进去啊

复杂度:
时间: O(n)
空间: O(n)

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 300005
#define maxe 600005
using namespace std;
int n,m,tot,len,son[maxe],nxt[maxe],lnk[maxn],dep[maxn],fa[maxn][20],w[maxn],s[maxn],t[maxn],la[maxn],ans[maxn],ha[1000005],hb[1000005];
struct data{
    int tot,son[maxn],nxt[maxn],lnk[maxn]; 
    void add(int x,int y){
        son[++tot]=y; nxt[tot]=lnk[x]; lnk[x]=tot;
    }
}A,B,C,D;
inline char nc(){
    static char buf[100000],*pa=buf,*pb=buf;
    return pa==pb&&(pb=(pa=buf)+fread(buf,1,100000,stdin),pa==pb)?EOF:*pa++;
}
inline void readi(int &x){
    x=0; char ch=nc();
    while ('0'>ch||ch>'9') ch=nc();
    while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=nc();}
}
void _add(int x,int y){
    son[++tot]=y; nxt[tot]=lnk[x]; lnk[x]=tot;
}
void _dfs(int x,int dad){
    for (int j=lnk[x];j;j=nxt[j])
        if (son[j]!=dad) {dep[son[j]]=dep[x]+1; _dfs(son[j],x); fa[son[j]][0]=x;}
}
void _init(){
    freopen("running.in","r",stdin);
    freopen("running.out","w",stdout);
    readi(n); readi(m);
    for (int i=1,x,y;i<n;i++){
        readi(x); readi(y); _add(x,y); _add(y,x);
    }
    for (int i=1;i<=n;i++) readi(w[i]);
    dep[1]=1; _dfs(1,-1); len=log2(n);
    for (int j=1;j<20;j++)
        for (int i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1];
}
int _LCA(int x,int y){
    if (dep[x]<dep[y]) swap(x,y);
    for (int j=len;j>=0&&dep[x]>dep[y];j--) if (dep[fa[x][j]]>=dep[y]) x=fa[x][j];
    if (x==y) return x;
    for (int j=len;j>=0;j--) if (fa[x][j]!=fa[y][j]) {x=fa[x][j]; y=fa[y][j];}
    return fa[x][0];
}
void _count(int x,int dad){
    int lsta=ha[dep[x]+w[x]+maxn],lstb=hb[dep[x]-w[x]+maxn];
    for (int j=lnk[x];j;j=nxt[j])
        if (son[j]!=dad) _count(son[j],x);
    for (int j=A.lnk[x];j;j=A.nxt[j]) ha[A.son[j]+maxn]++;
    for (int j=B.lnk[x];j;j=B.nxt[j]) hb[B.son[j]+maxn]++;
    ans[x]+=ha[dep[x]+w[x]+maxn]-lsta+hb[dep[x]-w[x]+maxn]-lstb;
    for (int j=C.lnk[x];j;j=C.nxt[j]) ha[C.son[j]+maxn]--;
    for (int j=D.lnk[x];j;j=D.nxt[j]) hb[D.son[j]+maxn]--;
}
void _solve(){
    memset(ha,0,sizeof(ha));
    memset(hb,0,sizeof(hb));
    memset(ans,0,sizeof(ans));
    for (int i=1;i<=m;i++){
        readi(s[i]); readi(t[i]); la[i]=_LCA(s[i],t[i]); int dis=dep[s[i]]+dep[t[i]]-(dep[la[i]]<<1);
        A.add(s[i],dep[s[i]]); B.add(t[i],dep[t[i]]-dis); C.add(la[i],dep[s[i]]); D.add(la[i],dep[t[i]]-dis);
    }
    _count(1,-1);
    for (int i=1;i<=m;i++) if (dep[s[i]]-dep[la[i]]==w[la[i]]) ans[la[i]]--;
    for (int i=1;i<n;i++) printf("%d ",ans[i]); printf("%d",ans[n]);
}
int main()
{
    _init();
    _solve();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/try__jhf/article/details/78369183