Codeforces Round #495 (Div. 2)E. Sonya and Ice Cream(树的直径+单调队列/逆向思维)

E. Sonya and Ice Cream

题意:

输入 n , k ( 1 ≤ k ≤ n ≤ 1 0 5 ) n,k(1\leq k\leq n\leq 10^5) n,k(1kn105)
接下来 n − 1 n-1 n1行,每行 u , v , w u,v,w u,v,w表示树边;
问在该树上选一条不超过 k k k个点的路径,使其它点到该路径最大距离最小。

题解1(树的直径+单调队列):

很容易想到 k k k个点在直径上,然后滑动窗口就好了。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+9;
struct Edge{
    int v,w,nxt;
}e[N<<1];
int head[N],cnt;
inline void add(int u,int v,int w){
    e[cnt]=(Edge){v,w,head[u]};
    head[u]=cnt++;
}
int n,k,mx,s,t;
int f[N],d[N];
void dfs(int u,int fa){
    if(d[mx]<d[u])mx=u;f[u]=fa;
    for(int i=head[u];~i;i=e[i].nxt){
        Edge e1=e[i];
        if(e1.v==fa)continue;
        d[e1.v]=d[u]+e1.w;
        dfs(e1.v,u);
    }
}
vector<int>path;
bool vis[N];
int dfs2(int u,int fa){
    int mx=0; 
    for(int i=head[u];~i;i=e[i].nxt){
        Edge e1=e[i];
        if(e1.v==fa||vis[e1.v])continue;
        mx=max(mx,e1.w+dfs2(e1.v,u));
    }
    return mx;
}
int dis[N];
int ans=1e9;
int q[N],hd,tl;
int main(){
  //  freopen("tt.in","r",stdin),freopen("tt.out","w",stdout);
    cin>>n>>k;
    memset(head,-1,sizeof(head));
    for(int i=1,u,v,w;i<n;i++)cin>>u>>v>>w,add(u,v,w),add(v,u,w);
    mx=1,dfs(1,0);s=mx;
    d[s]=0;dfs(s,0);t=mx;
    while(mx)vis[mx]=1,path.push_back(mx),mx=f[mx];
   // cout<<s<<" "<<t<<endl;
   // for(auto i:path)cout<<i<<" ";cout<<endl;
    for(auto i:path)dis[i]=dfs2(i,0);
    hd=0;tl=-1;int l;
    for(int i=0;i<path.size();i++){
        l=max(0,i-k+1);
        while(hd<=tl&&i-q[hd]+1>k)hd++;
        while(hd<=tl&&dis[path[i]]>=dis[path[q[tl]]])tl--;q[++tl]=i;
        ans=min(ans,max(dis[path[q[hd]]],max(d[path[q[tl]]],d[t]-d[path[l]])));
     //   cout<<ans<<endl;
    }
    cout<<ans<<endl;
    return 0;
}

题解2(逆向思维):

从反方向考虑这个问题,贪心地依次删去叶子节点,直到节点数小于 k k k,叶子节点个数小于等于2,即得到了路径。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+9;
int n,k;
#define P pair<int,int>
set<P>g[N],s;
int ans;
int main(){
   // freopen("tt.in","r",stdin),freopen("tt.out","w",stdout);
    cin>>n>>k;
    for(int i=1,u,v,w;i<n;i++)cin>>u>>v>>w,g[u].insert(P(v,w)),g[v].insert(P(u,w));
    for(int i=1;i<=n;i++)if(g[i].size()==1)s.insert(P((*g[i].begin()).second,i));
    while(n>k||s.size()>2){
        P p1=*s.begin();s.erase(p1);n--;
        ans=p1.first;
        int v=(*g[p1.second].begin()).first;
        g[v].erase(g[v].lower_bound(P(p1.second,0)));
        if(g[v].size()==1)s.insert(P((*g[v].begin()).second+ans,v));
    }
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值