zoukankan      html  css  js  c++  java
  • [Sdoi2011]消防


    Time Limit: 10 Sec Memory Limit: 512 MB
    某个国家有n个城市,这n个城市中任意两个都连通且有唯一一条路径,每条连通两个城市的道路的长度为zi(zi<=1000)。
    这个国家的人对火焰有超越宇宙的热情,所以这个国家最兴旺的行业是消防业。由于政府对国民的热情忍无可忍(大量的消防经费开销)可是却又无可奈何(总统竞选的国民支持率),所以只能想尽方法提高消防能力。
    现在这个国家的经费足以在一条边长度和不超过s的路径(两端都是城市)上建立消防枢纽,为了尽量提高枢纽的利用率,要求其他所有城市到这条路径的距离的最大值最小。
    你受命监管这个项目,你当然需要知道应该把枢纽建立在什么位置上。
    Input
    输入包含n行:
    第1行,两个正整数n和s,中间用一个空格隔开。其中n为城市的个数,s为路径长度的上界。设结点编号以此为1,2,……,n。
    从第2行到第n行,每行给出3个用空格隔开的正整数,依次表示每一条边的两个端点编号和长度。例如,“2 4 7”表示连接结点2与4的边的长度为7。
    Output
    输出包含一个非负整数,即所有城市到选择的路径的最大值,当然这个最大值必须是所有方案中最小的。
    Sample Input
    【样例输入1】
    5 2
    1 2 5
    2 3 2
    2 4 4
    2 5 3

    【样例输入2】
    8 6
    1 3 2
    2 3 2
    3 4 6
    4 5 3
    4 6 4
    4 7 2
    7 8 3
    Sample Output

    【样例输出1】
    5
    【样例输出2】

    5
    HINT

    对于100%的数据,n<=300000,边长小等于1000。

    /*
    证明1:对于直径上的点,离它最远的点一定是直径上的某个点
    假设直径为ac
            e
            .
            .
            . 
    a.......b.....c(设ab>bc)
            .
    		.
    		.
    		.
    		d
    对于b点来说,离它最远的应该是a点
    如果不是a,而是d,则(c,b,d)应该是直径 
    
    证明2:对于非直径上的点d, 离它最远的点也一定是直径上的某个点
    假如离d最远的不是a,而是e.
    则db+be>db+ab
    于是be>ab,也就是说e离b,比a离b更远,这也开始的证明相违背。
    
    同理db<bc.
    
    以上我们可以理解成对于以b为根一棵树,ab,bc为其最长链与次长链。
     
     
    l...........t.......i.........r
    l,r为直径的左右端点,i为我们枚举的一个点,t为另一个点(初值为i的父亲点)
    [t,i]的距离不超过规定的Len
    则直径上没有被选中的,到选中的边的距离,按要求尽可能小,于是取
    ans=min(ans,max(d[t],d[r]-d[i]));
    d数组代表每个点到l的距离。 
    由于树上所有点,离它们最远的点,一定是直径的两个端点之一(其实到两个端点的值都小)
    对于非直径上的边分两种来讨论
    一种是与直径的交点,不在我们所选的线段内部,
    设为x,与直径交点为y.则xy的长度一定小于y到直径端点的距离。
    另一种是与直径的交点,在我们所选线段内部,这个我们要暴力找一下就好了。
    如果说还更精确的,就是直径的中点是一定会被选中的,然后从它向左,或向右去找一找,但似乎优化意见不大。
    */ #include<iostream> #include<cstdio> #include<cstring> using namespace std; int fa[310100],vis[301000],last[301000],len=0,d[301000]; struct node { int to,next,w; }a[601000]; void add(int a1,int a2,int a3) { len++; a[len].to=a2; a[len].w=a3; a[len].next=last[a1]; last[a1]=len; } void dfs(int x) { for(int i=last[x];i;i=a[i].next) { int to=a[i].to; if(vis[to]||fa[x]==to) continue; fa[to]=x; d[to]=d[x]+a[i].w; dfs(to); } } int main() { int n,s,x,y,z,ans=2147483647; cin>>n>>s; for(int i=1;i<n;i++) { scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } int l=1,r=1; dfs(l); for(int i=1;i<=n;i++) if(d[i]>d[l]) l=i; memset(fa,0,sizeof(fa)); d[l]=0; dfs(l);//算出每个点到L的距离,l是直径的左端点 for(int i=1;i<=n;i++) if(d[i]>d[r]) r=i; int t=r; //找出直径的右端点 for(int i=r;i;i=fa[i])//尺取法 { while(fa[t]&&d[i]-d[fa[t]]<=s) t=fa[t]; ans=min(ans,max(d[t],d[r]-d[i])); } for(int i=r;i;i=fa[i]) //找出直径上的点 vis[i]=1; for(int i=r;i;i=fa[i]) d[i]=0,dfs(i);//以直径上每个点为根,找出非直径上的点到它们的距离 for(int i=1;i<=n;i++) //在找出这些距离后,求出其最大值 if(vis[i]==0) ans=max(ans,d[i]); cout<<ans; }

      

     

      

    下面这个做法更好想一些:

    先找出直径,再找出所有非直径上的点到直径上点的距离的最大值,也就是说当我们选择的线段为整个直径时,它就是答案了。

    但事实上我们并不能选择整个直径,于是将这个最大值做为L,将直径的长度做为R,二分一个Limit出来。然后从直径的两个端点向内走,使得所选的线段与直径两个端点的距离<=limit,定下点来后,再看选择的线段长度是否<=s,即系统规定的值。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
     
    const int BufferSize = 1 << 16;
    char buffer[BufferSize], *Head, *Tail;
    inline char Getchar() {
        if(Head == Tail) {
            int l = fread(buffer, 1, BufferSize, stdin);
            Tail = (Head = buffer) + l;
        }
        return *Head++;
    }
    int read() {
        int x = 0, f = 1; char c = getchar();
        while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
        while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
        return x * f;
    }
     
    #define maxn 300010
    #define maxm 600010
     
    int n, m, head[maxn], nxt[maxm], to[maxm], dist[maxm], lim, A, B;
     
    void AddEdge(int a, int b, int c) {
        to[++m] = b; dist[m] = c; nxt[m] = head[a]; head[a] = m;
        swap(a, b);
        to[++m] = b; dist[m] = c; nxt[m] = head[a]; head[a] = m;
        return ;
    }
     
    int Q[maxn], hd, tl, d[maxn], fa[maxn], fad[maxn];
    void bfs() {
        while(hd < tl) {
            int u = Q[++hd];
            for(int e = head[u]; e; e = nxt[e]) if(d[to[e]] < 0) {
                d[to[e]] = d[u] + dist[e];
                fa[to[e]] = u; fad[to[e]] = dist[e];
                Q[++tl] = to[e];
            }
        }
        return ;
    }
     
    int diap[maxn], dis[maxn], cntd;
    bool check(int x) 
    {
        int l = 1, r = cntd, tmp = x;
        while(x && l < cntd) 
    	{
            if(x < dis[l+1] - dis[l]) break;
            x -= dis[l+1] - dis[l]; l++;
        }
        while(tmp && r > 1) {
            if(tmp < dis[r] - dis[r-1]) break;
            tmp -= dis[r] - dis[r-1]; r--;
        }
        return dis[r] - dis[l] <= lim;
    }
     
    int main() {
        n = read(); lim = read();
        for(int i = 1; i < n; i++) {
            int a = read(), b = read(), c = read();
            AddEdge(a, b, c);
        }
         
        memset(d, -1, sizeof(d));
        hd = tl = 0; Q[++tl] = 1; d[1] = 0;
        bfs();
        for(int i = 1; i <= n; i++)
    	    if(d[A] < d[i]) A = i;
        memset(d, -1, sizeof(d));
        hd = tl = 0; Q[++tl] = A; d[A] = 0;
        bfs();
        for(int i = 1; i <= n; i++) 
    	   if(d[B] < d[i]) B = i;
         
        int u = B; 
    	diap[++cntd] = u; 
    	dis[cntd] = 0;
        while(u != A) 
    	{
            diap[++cntd] = fa[u]; 
    		dis[cntd] = dis[cntd-1] + fad[u];
            u = fa[u];
        }
        int l = 0, r = d[B];
        memset(d, -1, sizeof(d));
        hd = tl = 0;
        for(int i = 1; i <= cntd; i++) //将直径上的点进队列 
    	    Q[++tl] = diap[i], d[diap[i]] = 0;
        bfs();//找其它点到直径的距离 
        for(int i = 1; i <= n; i++) //取出最大值做为下界,直径的值做为上界 
    	   l = max(l, d[i]);
        while(l < r) 
    	{
            int mid = l + r >> 1;
            if(!check(mid)) l = mid + 1; else r = mid;
        }
         
        printf("%d\n", l);
         
        return 0;
    }
    

      

  • 相关阅读:
    黑客悬赏活动第二期 | 百万ELF赏金,aelf跨链转账标准协议CCTP等你挑战!
    2020年aelf首场全民公测,有奖狂欢四重好礼大放送!
    使用aelf最新稳定测试币AEUSD试玩BingoGame Demo,赢取体验奖金!
    开发者大赛 | aelf轻型DApp开发训练大赛结果公布!
    黑客赏金第一期 | aelf跨链转账标准协议准备就绪,88888ELF赏金等你挑战!
    Twitter AMA预告 | aelf 创始人马昊伯将以【aelf治理与发展】为主题进行在线答疑!
    aelf Enterprise 1.0.0 Preview 2 版正式发布!
    aelf技术点解读 | 分红合约接口实现方案
    深入浅出索引--Mysql45讲笔记记录 打卡day3
    一条SQL语句是如何执行的?--Mysql45讲笔记记录 打卡day1
  • 原文地址:https://www.cnblogs.com/cutemush/p/11766421.html
Copyright © 2011-2022 走看看