题解:P9464 [EGOI 2023] Padel Prize Pursuit / 追梦笼式网球
为了方便,我在接下来的题解和代码中会将选手和比赛的编号全部加 \(1\)。
首先可以发现,当一个人击败另一个人之后,败者此时拥有的奖牌接下来的移动都是一样的了。
考虑如何应用这个性质,我们可以对所有的对局建边,边由胜者指向败者,边权为这场对局的编号(表示胜者除了获得败者的所有奖牌外,还有一个编号为当前对局编号的奖牌)。由于此时败者的所有奖牌都被清空了,所以我们要给败者建一个新点以便后续建边使用。这样我们就得到了一张图(易证这是个有向边组成的森林),反向记录了奖牌的转移顺序。由于一场对局结束后胜者的这些奖牌会在他手中停留直到他下一次被击败,所以我们记录一下第 \(x\) 个点被击败的时间 \(t_x\)。如果这个点对应的时刻到 \(m\) 场对局全部结束都没有被击败则将 \(t_x\) 设为 \(m+1\)。
接下来考虑怎么统计答案。首先开一个桶,用来统计当前这块奖牌被 \(n\) 位选手分别占有过几天。然后从入度为 \(0\) 的点开始 dfs,每走过一条 \(x \to y\) 边权为 \(w\) 的边,说明第 \(w\) 个奖牌在 \(x\) 手里停留了 \(t_x-w\) 天。接下来就很简单了,下传 \(x\)、\(t_x\) 和上块牌的答案,更新桶,再检查是否要更新答案。最后直接统计每位选手拥有的奖牌数即可。
#include<bits/stdc++.h>
#define MAXN 1000005
#define int long long
using namespace std;
const int inf=1e18;
int n,m,a[MAXN],b[MAXN],ans[MAXN],cnt,head[MAXN],dcnt,c[MAXN],d[MAXN],t[MAXN],sum[MAXN],rd[MAXN],cd[MAXN];
struct Edge{
int value,next,w;
}edge[MAXN];
void addedge(int u,int v,int w){
edge[++cnt].value=v;
edge[cnt].next=head[u];
edge[cnt].w=w;
head[u]=cnt;
}
int mp[MAXN];
void dfs(int x,int p,int pt,int pw,int maxn,int tans){
mp[d[p]]+=(pt-pw);
if(mp[d[p]]>maxn||(mp[d[p]]==maxn&&d[p]<tans)){
maxn=mp[d[p]];
tans=d[p];
}
ans[pw]=tans;
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].value,w=edge[i].w;
if(y!=p){
dfs(y,x,t[x],w,maxn,tans);
}
}
mp[d[p]]-=(pt-pw);
}
signed main(){
// freopen("ybt.in","r",stdin);
// freopen("ybt.out","w",stdout);
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)c[i]=i,d[i]=i;;
dcnt=n;
for(int i=1;i<=m;i++){
scanf("%lld%lld",&a[i],&b[i]);a[i]++,b[i]++;
addedge(c[a[i]],c[b[i]],i);rd[c[b[i]]]++,cd[c[a[i]]]++;
t[c[b[i]]]=i;
c[b[i]]=++dcnt;
d[dcnt]=b[i];
}
for(int i=1;i<=dcnt;i++){
if(cd[i]&&!rd[i]){
t[i]=m+1;
dfs(i,0,0,0,0,0);
}
}
for(int i=1;i<=m;i++)sum[ans[i]]++;
for(int i=1;i<=n;i++)printf("%lld ",sum[i]);
return 0;
}
浙公网安备 33010602011771号