zoukankan      html  css  js  c++  java
  • 「CF1004E」Sonya and Ice Cream

    题目描述

    给定一个 (N) 个点的树,要选出一条所含点的个数不超过 (K) 的一条路径,使得路径外的点到这条路径的距离的最大值最小。
    数据范围:(1le K le N le 10^5)

    解题思路

    这道题我有两种方法。

    方法一

    我们考虑一个性质:选出来的链一定会是直径的一部分
    不然就肯定会存在可能更新最大值的一个分支,而且这个分支的大小一定会不比路径包含在直径上时小。
    同样的道理,我们发现这条路径在直径上越长越好。
    那么我们不妨先把直径抠出来,记作一个序列,那么这颗树就可以想象成一条链挂了很多子树。
    我们dfs出每一个直径上的点,它下面的子树的以根为源点的最长路,然后用 ( ext{ST}) 表维护一下它的区间最大值,再加上一点前缀和,然后在直径上移动这个序列,更新答案就好了。
    复杂度是 (O(n log n)) 的。

    方法二

    考虑一种贪心的思想。
    我们考虑不断删掉叶子,每次选择距离目标形态最近的叶子删掉,并且判断一下他的父亲是不是成了新的叶子,并将他的答案向父亲合并。
    关于正确性:

    1. 删成链之后就会继续删链的端点,这肯定没问题。
    2. 这里的最近类似与上文中的“直径上的点的子树的以根为源点的最长路”,所以是对的。
    3. 由于选择顺序的规定,所以更新父亲的一定是最后被选的一个儿子,因为它的关键字最大,所以只需要在父亲变叶子时更新。

    知道了原理后,我们可以用 set 存边,方便进行删边操作,用 priority_queue 来维护所有的叶子,然后直接模拟这个过程就好了。

    细节注意事项

    • 用第一种方法会稍微有一点难调。
    • 用第二种方法就可能会出现 STL 的使用出错等问题。

    参考代码

    这是方法一的代码,由于我自己没(lan)有(de)打,所以这里放的是 ( ext{color{black}{M}color{red}{\_sea}}) 神仙的代码

    // ===================================
    //   author: M_sea
    //   website: http://m-sea-blog.com/
    // ===================================
    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #define re register
    using namespace std;
    
    inline int read() {
        int X=0,w=1; char c=getchar();
        while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
        while (c>='0'&&c<='9') X=X*10+c-'0',c=getchar();
        return X*w;
    }
    
    const int N=100000+10;
    
    int n,k;
    
    struct edge { int v,w,nxt; } e[N<<1];
    int head[N];
    inline void addEdge(int u,int v,int w) {
        static int cnt=0;
        e[++cnt]=(edge){v,w,head[u]},head[u]=cnt;
    }
    
    int rt1,rt2;
    int dis[N],fa[N];
    inline void dfs1(int u,int f) {
        if (dis[u]>dis[rt1]) rt1=u;
        for (re int i=head[u];i;i=e[i].nxt)
            if (e[i].v!=f) dis[e[i].v]=dis[u]+e[i].w,dfs1(e[i].v,u);
    }
    inline void dfs2(int u,int f) {
        fa[u]=f;
        if (dis[u]>dis[rt2]) rt2=u;
        for (re int i=head[u];i;i=e[i].nxt)
            if (e[i].v!=f) dis[e[i].v]=dis[u]+e[i].w,dfs2(e[i].v,u);
    }
    
    int cnt=0,lend=0;
    int in[N],maxdis[N],w[N];
    int lg[N],st[17][N],sum[N];
    inline void dfs3(int u,int f) {
        for (re int i=head[u];i;i=e[i].nxt) {
            int v=e[i].v; if (v==f) continue;
            w[v]=e[i].w,dfs3(v,u);
            if (!in[v]) maxdis[u]=max(maxdis[u],maxdis[v]+e[i].w);
        }
    }
    inline void init_diameter() {
        for (re int i=rt2;i;i=fa[i]) in[i]=1,++cnt;
        dfs3(rt1,0);
        for (re int i=2;i<=n;++i) lg[i]=lg[i>>1]+1;
        for (re int i=rt2,j=1;i;i=fa[i],++j) st[0][j]=maxdis[i];
        for (re int i=1;i<17;++i)
            for (re int j=1;j+(1<<i)-1<=cnt;++j)
                st[i][j]=max(st[i-1][j],st[i-1][j+(1<<(i-1))]);
        for (re int i=rt2,j=1;i;i=fa[i],++j) sum[j]=w[i],lend+=w[i];
        for (re int i=1;i<=cnt;++i) sum[i]+=sum[i-1];
    }
    inline int query(int l,int r) {
        int t=lg[r-l+1];
        return max(st[t][l],st[t][r-(1<<t)+1]);
    }
    
    int main() {
        n=read(),k=read();
        if (n==1) { puts("0"); return 0; }
        for (re int i=1;i<n;++i) {
            int u=read(),v=read(),w=read();
            addEdge(u,v,w),addEdge(v,u,w);
        }
        dfs1(1,0),dfs2(rt1,0);
        init_diameter();
        if (k>cnt) printf("%d
    ",query(1,cnt));
        else {
            int ans=2e9;
            for (re int i=1;i+k-1<=cnt;++i) {
                int tmp=max(sum[i-1],lend-sum[i+k-2]);
                ans=min(ans,max(tmp,query(i,i+k-1)));
            }
            printf("%d
    ",ans);
        }
        return 0;
    }
    

    下面是方法二的代码,我自己敲的。

    /*
      ================================
      Author: The Ace Bee
      Blog: www.cnblogs.com/zsbzsb
      This code is made by The Ace Bee
      ================================
    */
    #include <algorithm>
    #include <iostream>
    #include <cstring>
    #include <cstdlib>
    #include <cstdio>
    #include <cctype>
    #include <cmath>
    #include <ctime>
    #include <queue>
    #include <set>
    #define rg register
    using namespace std;
    template < typename T > inline void read(T& s) {
     	s = 0; int f = 0; char c = getchar();
     	while (!isdigit(c)) f |= (c == '-'), c = getchar();
     	while (isdigit(c)) s = s * 10 + (c ^ 48), c = getchar();
     	s = f ? -s : s;
    }
    
    const int _ = 100000 + 10;
    
    int n, k, ans;
    struct node{ int u, w; };
    inline bool operator < (const node& x, const node& y) { return x.w > y.w; }
    priority_queue < node > Q;
    set < pair < int, int > > s[_];
    
    int main() {
    #ifndef ONLINE_JUDGE
    	freopen("in.in", "r", stdin);
    #endif
    	read(n), read(k);
    	for (rg int u, v, d, i = 1; i < n; ++i) {
    		read(u), read(v), read(d);
    		s[u].insert(make_pair(v, d));
    		s[v].insert(make_pair(u, d));
    	}
    	for (rg int i = 1; i <= n; ++i)
    		if (s[i].size() == 1) Q.push((node) { i, (*s[i].begin()).second });
    	while (Q.size() > 2 || k < n) {
    		node x = Q.top(); Q.pop(), --n, ans = x.w;
    		int u = x.u, v = (*s[u].begin()).first;
    		s[v].erase(s[v].lower_bound(make_pair(u, 0)));
    		if (s[v].size() == 1) Q.push((node) { v, ans + (*s[v].begin()).second });
    	}
    	printf("%d
    ", ans);
    	return 0;
    }
    
    

    完结撒花 (qwq)

  • 相关阅读:
    当老板如何带团队?
    创业者第一法宝-了解自己
    交流才能交易,交易才能交心
    集合框架
    MySQL一些命令语法
    JS组成整理
    git中可以pull但是push提示Everything up-to-date的情况
    循环判断以及文件的使用--练习1
    Hello World !
    linux文件权限修改
  • 原文地址:https://www.cnblogs.com/zsbzsb/p/11728477.html
Copyright © 2011-2022 走看看