LGP1600_1 天天爱跑步(高妙统计贡献) 笔记
原题链接:传送门
题意简述
给定一棵
解决方案
最暴力的做法是模拟每个玩家的跑步过程,这么做在数据随机的情况下是
那么这么做有没有可行性呢?我们来分析一下一个玩家给一个观察员造成贡献的条件。
首先,我们令
对于观察员
必须在 的子树内部,且 必须在 的子树外部或与 重合,否则整条路径压根不经过 。 ,这样此玩家才能刚好在第 秒走到 。
同理,对于观察员
必须在 的子树内部,且 必须在 的子树外部或与 重合,否则整条路径压根不经过 。 。这个式子看起来不太好懂?可以这么理解:如果把路径抻直,那么第 时刻时玩家就在 的深度。
既然我们要让观察员“自取”贡献,那么我们就要开桶把这些贡献存进去,然后观察员顺着下标就取出来了。另外还有两点要注意:
- 如果刚好有玩家在
处做出了贡献,那么会算重。我们要提前减掉多算的一次(见代码) 和 都有可能是负的,因此代码实现上要给其加一个偏移量 ,相应地桶数组也要开两倍。
代码
#include <bits/stdc++.h>
using namespace std;
const int MaxN=6e5+5;
int frdint(){
int n=0,k=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')k=-1;ch=getchar();}
while(isdigit(ch))n=(n<<3)+(n<<1)+ch-'0',ch=getchar();
return n*k;
}
void fwrint(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)fwrint(x/10);
putchar(x%10+'0');
}
int N,M,X,Y,W[MaxN];
vector<int> Ti[MaxN];
void addudge(int u,int v){
Ti[u].push_back(v);
Ti[v].push_back(u);
}
int dep[MaxN],tfa[MaxN],siz[MaxN],hvs[MaxN];
void dfs1(int u,int f){
dep[u]=dep[f]+1,tfa[u]=f,siz[u]=1;
for(int v : Ti[u]){
if(v==f)continue;
dfs1(v,u);siz[u]+=siz[v];
if(siz[v]>siz[hvs[u]])hvs[u]=v;
}
}
int top[MaxN];
void dfs2(int u,int t){
top[u]=t;if(!hvs[u])return;
dfs2(hvs[u],t);for(int v : Ti[u]){
if(v!=tfa[u]&&v!=hvs[u])dfs2(v,v);
}
}
int getlca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=tfa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
struct anob{int s,t,d;}P[MaxN];
int buc[2][MaxN<<1],ans[MaxN];
vector<int> avec[MaxN],svec[MaxN],tvec[MaxN];
void dfs3(int u){
int tmp1=buc[0][dep[u]+W[u]],tmp2=buc[1][W[u]-dep[u]+N];
for(int v : Ti[u])if(v!=tfa[u])dfs3(v);
buc[0][dep[u]]+=svec[u].size();
for(int i : tvec[u])buc[1][P[i].d-dep[P[i].t]+N]++;
int cres=buc[0][dep[u]+W[u]]-tmp1+buc[1][W[u]-dep[u]+N]-tmp2;
if(cres>=0)ans[u]+=cres;
for(int i : avec[u])buc[0][dep[P[i].s]]--;
for(int i : avec[u])buc[1][P[i].d-dep[P[i].t]+N]--;
}
int main(){
N=frdint(),M=frdint();
for(int i = 1;i < N;i++){
X=frdint(),Y=frdint();
addudge(X,Y);
}
for(int i = 1;i <= N;i++)W[i]=frdint();
dfs1(1,0),dfs2(1,1);
for(int i = 1;i <= M;i++){
auto &[cs,ct,cd]=P[i];
cs=frdint(),ct=frdint();
svec[cs].push_back(i);
tvec[ct].push_back(i);
int anc=getlca(cs,ct);
avec[anc].push_back(i);
cd=dep[cs]+dep[ct]-dep[anc]*2;
if(dep[anc]+W[anc]==dep[cs])ans[anc]--;
}
dfs3(1);
for(int i = 1;i <= N;i++){
fwrint(ans[i]),putchar(' ');
}
return 0;
}
反思与总结
这道题的此做法中,我们在算贡献时从考虑每条玩家的路径转而考虑每个结点的贡献。这启示我们要考虑从不同的角度和对象计算贡献,找到最优的那个。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!