避难向导

题目描述
“特大新闻,特大新闻!全国爆发了一种极其可怕的病毒,已经开始在各个城市 中传播开来!全国陷入了巨大的危机!大量居民陷入恐慌,想要逃到其它城市以 避难!经调查显示,该病毒来自于C 市的A 学校的一次非法的……” “哎。”你关上电视,叹了口气。作为A 学校的校长,你一天前为了保命,独自 逃离了A 学校,抛弃了全校师生,包括那个曾经帮你计算并拆除道路的工程师。 你良心受到了巨大的谴责,因此决定做出一些补救,回答一些逃难的人提出的询 问。 已知该国一共有n 个城市,并且1 号城市是首都。(n-1)条双向的公路连接这些 城市,通过这些公路,任意两个城市之间存在且仅存在一条路径。每条公路有一 个长度。如果一个城市只与一条公路相连,则称它为边境城市。 该国政府有一个奇怪的规定:每个城市有一个封闭系数di,定义di 为离这个城 市最远的边境城市到这个城市的距离。市民们认为,一个城市的安全系数Si 和 它的封闭系数有很重要的联系。a,b,c 是该国的幸运数字,所以大家公认一个 城市的安全系数Si = (di + a) * b mod c。 市民们一共会提出m 次询问。每个询问包含三个信息,xi,yi 和qi。xi 是询问 者所在的城市编号。你要为这个询问者在xi 到yi 的必经之路上找出一个离xi 最近的避难城市,并且要求这个避难城市的安全系数大于等于qi。如果存在这 样的城市(包含xi 和yi),则输出城市编号,否则输出一行包括一个数-1。

输入
第一行五个数:依次是n, m, a, b, c。 接下来n-1 行描述公路的信息。每行三个数,前两个数代表这条公路连接的两个 城市的编号,第三个数表示这条公路的长度。 再接下来m 行,每行描述一个询问,包含三个数xi, yi 和qi。

输出
对于每个询问,输出一行包含一个整数,存在符合要求的城市则输出城市编号, 不存在则输出-1。

样例输入
7 6 5 6 20
1 2 4
2 4 2
2 5 3
1 3 5
3 6 6
6 7 7
7 5 15
3 4 5
5 4 2
4 5 2
6 6 10
3 5 19

样例输出
6
3
2
4
6
-1

来源
by azui

azui大神的题解:

shelter(树上路径相关)

  • 题意:给出一棵树,根节点为1。树上每个节点的权值通过以它为端点的树上最长链通过带系数的计算获得。给出m个询问,每个询问给出三个信息 x, y, q,求从节点 x到 y的树上路径中离xi最近的同时权值大于等于 q(以下简称为合法)的节点的编号,不存在则输出-1。

  • 本题主要分为两个部分:求最长链,求两点间离其中一点最近的合法的点。
    前者的主要算法有四个: floyd, 以每个点为起点BFS,树 DP,找直径的端点,复杂度分别为
    O(n^3), O(n^2), O(n), O(n)。
    后者比较容易想到的单次询问O(n)的算法是从起点开始BFS,对于每一个点需要保存起点到
    它的路径中离起点最近的合法点编号,通过简单的状态转移可求得。
    n^3 + mn : 25~30分
    n^2 + mn : 35 分
    n +mn : 40~50 分

  • 对于树上路径,BFS显然是效率很低的方法,因为树上两点之间路径是唯一的,而 BFS太没
    有方向性。 比较好的方法是将无根树转化为有根树, 然后转化为与最近公共祖先相关的问题。
    具体为:设 z是x和y 的最近公共祖先,则问题等价于求x到z 的路径上离x最近的合法点
    以及求y到z的路径上离z 最近的合法点。如果暴力向上找最近公共祖先的话,单次查询复
    杂度为O(h),h为树的高度,但 h可能会被设计得很接近 n,但总体效果会快很多。
    n+mh : 55~60 分

  • 实际上,观察给出的数据范围发现,所有 y相等的数据可以特殊处理。从y开始BFS,对到达的每个点求出 y到这个点的路径上离y 最远的合法点,然后O(1)回答询问即可。
    n+mh & (特殊数据)n+m : 75分

  • 这题应该出题人自己没有找到比较好的离线算法,所以可以考虑优化在线算法的复杂度,可以优化到log^2(n),sqrt(n),log(n),前两种实现较简单,但不一定可以通过全部数据。

  • n+m*log^2(n) 算法:
    处理出倍增数组, 保存每个点的第2^k个祖先以及到第2^k个祖先的路径上最大的一个点权。
    定义一个函数实现 logn 时间求一个点到它的指定深度的祖先的路径中的最大点权,然后二
    分深度进行判断即可。

  • n+m*sqrt(h) 算法:
    将树的深度分成根号h层,处理出每个点到它所属的层顶节点的编号,以及它到层顶节点路
    径上的最大点权,先一层一层地向上找,然后一步一步地向上找即可,实现起来与其它普通
    的树分层没有太大区别。

  • n+m*log(n) 算法:
    因为出题人比较菜,用的是递归算法,不是很好描述。就把代码复制过来了:
    这两个函数分别是求一个点到深度为 upto 的祖先的路径上的离 x 最近的和离 x 最远的合法点,找不到返回-1。anc 和 f 是倍增数组,分别表示祖先和到祖先的路径上的最大点权(不含自身)。
    这两个递归函数中,每递归一层 d 值会减少,所以递归层数不超过 logn 层,函数体内部有一个循环,但容易发现这个循环在递归中只会执行一次,且这个循环复杂度也是 logn 的。
    综上,成功做到了 O(logn)的单次查询,可以轻松通过100 分的数据。具体见标程。

azui的代码:

#include<cstdio>
#include<cstring>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long
#define Max(a,b) ((a)>(b)?(a):(b))
const int MAXN = 100010;
const int MAXLG = 18;
LL A, B, C;
LL s[MAXN];
int N, M, LG;

struct Node {
    int to, d; Node *next;
}Edge[MAXN*2], *ecnt=Edge, *adj[MAXN];
inline void addedge(int a, int b, int c)
{
    (++ecnt)->to = b;
    ecnt->d = c;
    ecnt->next = adj[a];
    adj[a] = ecnt;
}

LL dis1[MAXN], dis2[MAXN];
int safety[MAXN];
int dep[MAXN];
int f[MAXN][MAXLG]; //不包括自己的
int anc[MAXN][MAXLG];
int p1, p2;
int prebfs(int s)
{
    queue<int> Q;
    int bst = s;
    Q.push(s);
    while (!Q.empty()) {
        int u = Q.front();
        Q.pop();
        if (dis1[u] > dis1[bst]) bst = u;
        for (Node*p = adj[u]; p; p=p->next)
            if (p->to != anc[u][0]) {
                dep[p->to] = dep[u] + 1;
                anc[p->to][0] = u;
                dis1[p->to] = dis1[u]+p->d;
                Q.push(p->to);
            }
    }
    return bst;
}
bool vis[MAXN];
int bfs(LL*dis, int s)
{
    queue<int> Q;
    int bst = s;
    memset(vis, 0, sizeof vis);
    Q.push(s);
    dis[s] = 0;
    while (!Q.empty()) {
        int u = Q.front();
        vis[u] = 1; Q.pop();
        if (dis[u] > dis[bst]) bst = u;
        for (Node*p = adj[u]; p; p=p->next)
            if (!vis[p->to]) {
                dis[p->to] = dis[u]+p->d;
                Q.push(p->to);
            }
    }
    return bst;
}
void Prepare()
{
    p1 = prebfs(1);
    p2 = bfs(dis1, p1);
    bfs(dis2, p2);
    LL tmp;
    for (int i = 1; i<=N; ++i) {
        tmp = Max(dis1[i], dis2[i]);
        safety[i] = (tmp + A) * B % C;
    }
    for (int i = 1; i<=N; ++i)
        f[i][0] = safety[anc[i][0]];
    for (int t = 1; t<=LG; ++t)
        for (int i = 1; i<=N; ++i) {
            anc[i][t] = anc[ anc[i][t-1] ][t-1];
            f[i][t] = Max(f[i][t-1], f[anc[i][t-1]][t-1]);
        }
}

int lca_dep(int x, int y)
{
    int i;
    if (dep[x] < dep[y]) swap(x, y);
    for (i = LG; i>=0; --i)
        if (dep[anc[x][i]] > dep[y])
            x = anc[x][i];
    if (dep[x] > dep[y]) x = anc[x][0];
    if (x==y) return dep[x];
    for (i = LG; i>=0; --i)
        if (anc[x][i] != anc[y][i])
            x = anc[x][i], y = anc[y][i];
    return dep[anc[x][0]];
}

int find1(int x, int d, int qi, int upto)
{
    if (dep[x] <= upto) return -1;
    for (; dep[anc[x][d]] < upto && d>=0; --d);
    if (f[x][d] < qi)
        return find1(anc[x][d], d-1, qi, upto);
    for (int i = d-1; i>=0; --i)
        if (f[x][i] < qi)
            x = anc[x][i];
    return anc[x][0];
}

int find2(int x, int d, int qi, int upto)
{
    if (dep[x] <= upto) return -1;
    for (; dep[anc[x][d]] < upto && d>=0; --d);
    int res = find2(anc[x][d], d-1, qi, upto);
    if (~res) return res;
    if (f[x][d] < qi) return -1;
    for (int i = d-1; i>=0; --i)
        if (f[anc[x][i]][i] >= qi)
            x = anc[x][i];
    return anc[x][0];
}

int adjust(int x, int upto)
{
    int d = LG;
    for (; !anc[x][d] || dep[anc[x][d]]<upto; --d);
    return d;
}

int solve(int xi, int yi, const int& qi)
{
    if (xi == yi)
        return safety[xi] >= qi ? xi : -1;
    int upto = lca_dep(xi, yi);
    if (upto == dep[yi]) { // y是x的直系祖先
        if (safety[xi] >= qi) return xi;
        return find1(xi, adjust(xi, upto), qi, upto);
    }
    if (upto == dep[xi]) { // x是y的直系祖先
        int res = find2(yi, adjust(yi, upto), qi, upto);
        if (~res) return res;
        return safety[yi] >= qi ? yi : -1;
    }
    //x不是y的直系祖先,y也不是x的直系祖先
    if (safety[xi] >= qi) return xi;
    int res = find1(xi, adjust(xi, upto), qi, upto);
    if (res!=-1) return res;
    res = find2(yi, adjust(yi, upto), qi, upto);
    if (res!=-1) return res;
    return safety[yi] >= qi ? yi : -1;
}

int main()
{
    //freopen("shelter.in", "r", stdin);
    //freopen("shelter.out", "w", stdout);

    int i, xi, yi, d;
    scanf("%d%d", &N, &M);
    for (; (1<<LG)<N; LG++);
    cin >> A >> B >> C;
    for (i = 1; i<N; ++i)
    {
        scanf("%d%d%d", &xi, &yi, &d);
        addedge(xi, yi, d);
        addedge(yi, xi, d);
    }
    Prepare();
    for (i = 1; i<=M; ++i)
    {
        scanf("%d%d%d", &xi, &yi, &d);
        printf("%d\n", solve(xi, yi, d));
    }
    return 0;
}

被惊艳了。。就这样做吧。
个人理解:充分利用做LCA的倍增思想

自己的:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXN 100000
#define MAXLG 18

struct node{
    int v,w;
    node *next;
}edge[2*MAXN+10],*adj[MAXN+10],*ecnt=&edge[0];
int n,Q,g[MAXN+10],fa[MAXN+10];
int sec[MAXN+10],f[MAXN+10][5],a,b,c,dep[MAXN+10];
int anc[MAXN+10][MAXLG+10],P[MAXN+10][MAXLG+10],LG;

void addedge(int u,int v,int w)
{
    node *p=++ecnt;
    p->v=v,p->w=w;
    p->next=adj[u];
    adj[u]=p;
}
void read()
{
    int x,y,w;
    scanf("%d%d%d%d%d",&n,&Q,&a,&b,&c);
    for(int i=1;i<n;i++){
        scanf("%d%d%d",&x,&y,&w);
        addedge(x,y,w);
        addedge(y,x,w);
    }
}
void dfs_cal_f(int u,int level)
{
    dep[u]=level;
    for(node *p=adj[u];p;p=p->next){
        int v=p->v,w=p->w;
        if(v==fa[u]) continue;
        fa[v]=u;
        dfs_cal_f(v,level+1);

        if(f[v][0]+w>f[u][0]){
            f[u][1]=f[u][0];
            f[u][0]=f[v][0]+w,g[u]=v;
        }
        else if(f[v][0]+w>f[u][1])
            f[u][1]=f[v][0]+w;
    }
}
void dfs_farthest(int u)
{
    sec[u]=((long long)max(f[u][3],f[u][0])+a)*b%c;
    for(node *p=adj[u];p;p=p->next){
        int v=p->v,w=p->w;
        if(v==fa[u]) continue;

        if(g[u]!=v)
            f[v][3]=f[u][0]+w;
        else
            f[v][3]=f[u][1]+w;
        f[v][3]=max(f[v][3],f[u][3]+w);
        dfs_farthest(v);
    }
}

void prepare_forLCA()
{
    memset(anc,0,sizeof anc);
    for(LG=0;1<<LG <=n;LG++);
    LG--;
    for(int i=1;i<=n;i++)
        anc[i][0]=fa[i],P[i][0]=sec[fa[i]]; //不包含自身
    for(int j=1;j<=LG;j++)
        for(int i=1;i<=n;i++){
            anc[i][j]=anc[anc[i][j-1]][j-1];
            P[i][j]=max(P[i][j-1],P[anc[i][j-1]][j-1]);
        }
}
int LCA(int x,int y)
{
    if(dep[x]<dep[y]) swap(x,y);
    int logx;
    for(logx=0;1<<logx <=dep[x];logx++);
    logx--;
    for(int i=logx;i>=0;i--)
        if(dep[anc[x][i]]>=dep[y])
            x=anc[x][i];
    if(x==y) return x;

    for(int i=logx;i>=0;i--)
        if(anc[x][i] && anc[x][i]!=anc[y][i])
            x=anc[x][i],y=anc[y][i];
    return fa[x];
}
int find1(int u,int upto,int qi,int d) //离u最近的合法点
{
    if(dep[u]<upto) return -1;
    for(;dep[anc[u][d]]<upto && d>=0;d--);
    if(P[u][d]<qi)
        return find1(anc[u][d],upto,qi,d-1);

    for(int i=d-1;i>=0;i--)
        if(P[u][i]<qi)
            u=anc[u][i];
    return fa[u]; //不包含自身
}
int find2(int u,int upto,int qi,int d) //离u最远的合法点
{
    if(dep[u]<upto) return -1;
    for(;dep[anc[u][d]]<upto && d>=0;d--);
    int ret=find2(anc[u][d],upto,qi,d-1);
    if(ret!=-1) return ret;
    if(P[u][d]<qi) return -1;
    for(int i=d-1;i>=0;i--)
        if(P[anc[u][i]][i]>=qi)
            u=anc[u][i];
    return fa[u];
}
int workout(int x,int y,int qi)
{
    int lca=LCA(x,y),upto=dep[lca],ret;

    if(sec[x]>=qi) return x;
    ret=find1(x,upto,qi,LG);
    if(ret!=-1) return ret;
    ret=find2(y,upto,qi,LG);
    if(ret!=-1) return ret;
    return sec[y]>=qi ? y:-1;
}
int main()
{
    int xi,yi,qi;
    read();
    dfs_cal_f(1,1);
    dfs_farthest(1);
    prepare_forLCA();

    for(int i=1;i<=Q;i++){
        scanf("%d%d%d",&xi,&yi,&qi);
        printf("%d\n",workout(xi,yi,qi));
    }
}

转载于:https://www.cnblogs.com/katarinayuan/p/6572863.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值