BZOJ4613 : [Wf2016]Longest Rivers

对于每条河流,要让它排名最靠前,那么显然它必须要延伸到根。

设第$i$条河流到根的距离为$d[i]$,对于每个节点,如果存在一条河流比$d[i]$长,那么让它延伸会使答案最小,否则要选择一条最短的河流来进行延伸。

设$f[i]$表示每个节点往外延伸的河流的长度的最小值,可以通过树形DP求出。

从小到大考虑每个$d$,取出所有超过了上一个$d$的限制,但是满足当前的$d$限制的临界点,这些点将不再是临界点。

若一个点所有的儿子都不是临界点,那么它的将变为临界点。

用堆按$f$从小到大维护临界点即可,答案就是临界点个数$+1$,也就是堆中元素个数$+1$。

因为每个点只会进堆、出堆各一次,所以时间复杂度为$O(n\log n)$。

 

#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<ll,int>P;
const int N=500010,M=1000010;
const ll inf=1LL<<60;
char name[N][12];
int n,m,cnt,i,x,y,w[M],g[M],nxt[M],fa[M],deg[M],a[N],ans[N];ll d[M],f[M];
priority_queue<P,vector<P>,greater<P> >q;
inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';}
inline bool cmp(int x,int y){return d[x]<d[y];}
inline void add(int x,int y){nxt[y]=g[x];g[x]=y;}
void dfs(int x){
  f[x]=x<=n?0:inf;
  for(int i=g[x];i;i=nxt[i]){
    d[i]=d[x]+w[i];
    fa[i]=x;
    dfs(i);
    if(f[i]<f[x])f[x]=f[i];
    deg[x]++;
  }
  f[x]+=w[x];
}
inline void up(int x){
  if(x==n+1)return;
  if(--deg[x])return;
  cnt++;
  q.push(P(f[x],x));
}
int main(){
  read(n),read(m);
  for(i=1;i<=n;i++){
    scanf("%s",name[i]);
    read(x),read(w[i]);
    add(x+n+1,i);
  }
  for(i=1;i<=m;i++)read(x),read(w[i+n+1]),add(x+n+1,i+n+1);
  dfs(n+1);
  f[n+1]=inf;
  for(i=1;i<=n;i++)a[i]=i;
  sort(a+1,a+n+1,cmp);
  cnt=n;
  for(i=1;i<=n;i++)q.push(P(f[i],i));
  for(i=1;i<=n;ans[a[i++]]=cnt)while(!q.empty()){
    P t=q.top();
    if(t.first>d[a[i]])break;
    q.pop();
    cnt--;
    up(fa[t.second]);
  }
  for(i=1;i<=n;i++)printf("%s %d\n",name[i],ans[i]+1);
  return 0;
}

  

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值