【树】【积累】Waterloo2019ICPC D.Deliveries (整除分块+树分块+括号序)

题解:Waterloo2019ICPC D.Deliveries (整除分块+树分块+括号序)

题目链接:cf Waterloo2019ICPC D.Deliveries

题意:

一棵 (N) 个顶点的带边权无根树,(Q) 个询问。每次询问 (S;F;T) ,表示从点 (S) 到点 (F) 用容量为 (T) 的电池前进,全程总共需要停下来多少次 。对于每个询问输出一行一个整数。

电池每当用完时,必须立刻停下来充电,后才能继续前进;每到达一个顶点必须停下来一次,停下来的这一次也可以用来充电。起点和终点需要各算一次停止次数。

时限:4s;空限:256MB。

数据范围:(1le N,Qle100000) , (1le w,Tle20000)

样例:

input

7 5
1 2 1
2 3 2
2 4 3
4 5 4
4 6 5
4 7 6
3 7 2
2 6 1
5 7 3
1 4 1
3 7 1

output

7
9
5
5
12

input

4 3
1 2 5
2 3 10
3 4 20
1 4 20
1 4 10
1 4 5

output

4
5
8

伪标程:(抄标程,改一下代码风格,加一些注释)

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn=5e5+5;

int head[maxn],to[maxn<<1],nxt[maxn<<1],cnt;
inline void add(int u,int v){
    to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
}
int din[maxn],dout[maxn],tnt,f[maxn][25];
void dfs(int u,int fa)
{
    din[u]=++tnt;f[u][0]=fa;
    for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1];
    for(int i=head[u];i;i=nxt[i])
        if(to[i]!=fa)dfs(to[i],u);
    dout[u]=++tnt;
}
inline bool upper(int u,int v){
    return din[u]<=din[v]&&dout[u]>=dout[v];
}
int LCA(int u,int v)
{
    if(upper(u,v))return u;
    for(int i=20;i>=0;i--)
        if(!upper(f[u][i],v))u=f[u][i];
    return f[u][0];
}
struct EG{
    int u,v;
}eg[maxn];
const int MAXN=2e4+5;
const int K=512;
struct Qu{
    int u,v,id;
};vector<Qu>qu[MAXN];
int *C[maxn];
int siz[maxn],dis[maxn],S[maxn],A[maxn];ll res[maxn];
ll cal(int l,int r)
{
    ll res=0;
    while(l<=r)
    {
        if(l%K==0&&l+K-1<=r){
            res+=S[l/K];l+=K;
        }
        else res+=A[l++];
    }
    return res;
}
int main()
{
    int n,q,u,v,w;
    scanf("%d%d",&n,&q);
    for(int i=1;i<n;i++){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v);add(v,u);eg[i]={u,v};
        w--;
        dis[i]=w;
        for(int l=1,r;l<=w;l=r+1){
            siz[l]++;r=w/(w/l);
        }
        siz[w+1]++;
    }
    for(int i=0;i<MAXN;i++)C[i]=new int[siz[i]+1];
    mem(siz,0);
    for(int i=1;i<=n-1;i++)C[1][siz[1]++]=i;
    dfs(1,1);
    for(int i=1;i<=q;i++){
        scanf("%d%d%d",&u,&v,&w);
        qu[w].push_back({u,v,i});
    }
    for(int i=1;i<=n-1;i++)if(din[eg[i].u]>din[eg[i].v])swap(eg[i].u,eg[i].v);
    for(int c=1;c<MAXN;c++)//枚举T
    {
        for(int i=0;i<siz[c];i++)
        {
            int id=C[c][i];//边的id
            int val=dis[id]/c;
            if(val){
                int cc=dis[id]/val+1;
                /*
                 在区间cc=[c,dis/(dis/c)]这个区间内,
                 dis/cc的值是一样的,都是 (val=dis/c)+1
                 */
                C[cc][siz[cc]++]=id;
            }
            val++;
            S[din[eg[id].v]/K]+=(val-A[din[eg[id].v]]);A[din[eg[id].v]]=val;
            S[dout[eg[id].v]/K]+=(-val-A[dout[eg[id].v]]);A[dout[eg[id].v]]=-val;
            /*
             此时 在块[id/K]里边更新块值和, 同时记录单点答案
             在求值时,当[l,r]的距离大于K时,不断加上块和,
             直到[l,r]距离小于K,再遍历累加单点值
             */
        }
        delete[] C[c];
        for(Qu qq:qu[c]){
            u=LCA(qq.u,qq.v);
            res[qq.id]=cal(din[u]+1,din[qq.u])+cal(din[u]+1,din[qq.v]);
        }
    }
    for(int i=1;i<=q;i++)printf("%lld
",res[i]+1);
}


叨叨:

T^T 这道题的做法对我来说真的是太难想到了。看标程的时候都 险些看不懂。

原文地址:https://www.cnblogs.com/kkkek/p/13661653.html