题解: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 这道题的做法对我来说真的是太难想到了。看标程的时候都 险些看不懂。