zoukankan      html  css  js  c++  java
  • 洛谷 P3629 [APIO2010]巡逻

    题目在这里

    这是一个紫题,当然很难。

    我们往简单的想,不建立新的道路时,从1号节点出发,把整棵树上的每条边遍历至少一次,再回到1号节点,会恰好经过每条边两次,路线总长度为$2(n-1)$,根据树的深度优先遍历思想,很容易证明这个结论,因为每条边必然被递归一次,回溯一次。

     

    建立1条新道路之后,因为新道路必须恰好经过一次(0次,2次都不可以),所以在沿着新道路(x,y)巡逻之后,要返回x,就必须沿着树上从y到x的路径巡逻一遍,最终形成一个环。与不建立新道路的情况相结合,相当于树上x与y之间的路径就只需经过一次了。

     

    因此,当$k=1$时,我们找到树的最长链,在两个端点之间加一条新道路,就能让总的巡逻距离最小。若树的直径为L,答案就是$2(n-1)-L+1$。

     

    建立第2条新道路(u,v)之后,又会形成一个环。若两条新道路形成的环不重叠,则树上u,v之间的路径只需经过一次,答案继续减小。否则,在两个环重叠的情况下,如果我们还按照刚才的方法把第2个环与建立1条新道路的情况相结合,两个环重叠的部分就不会被巡逻到。最终的结果是两个环重叠的部分由只需经过一次变回了需要经过两次。

     

    综上所述,我们得到了如下算法:

    1.在最初的树上求直径,设直径为$L1$。然后,把直径上的边权取反(从1改为-1)。

    2.在最长链边权取反之后的树上再次求直径,设直径为$L2$。

    答案就是$2(n-1)-(L1-1)-(L2-1)=2n-L1-L2$。如果L2这条直径包含L1取反的部分,就相当于两个环重叠。减掉$(L1-1)$后,重叠的部分变成了只需经过一次,减掉$(L2-1)$后,相当于把重叠的部分加回来,变回需要经过两次,与我们之前讨论相符。时间复杂度为$O(n)$。

     

    code:

    // luogu-judger-enable-o2
    #include <bits/stdc++.h>
    using namespace std;
    
    queue <int> q;
    const int N=100010;
    bool v[N];
    int f[N];
    int n,k,e=1,cnt,p;
    int s[N<<1][2],o[N],d[N],fa[N],w[N<<1];
    
    void add(int x,int y)
    {
        s[++e][0]=y;s[e][1]=o[x];o[x]=e;w[e]=1;
    }
    
    int bfs(int xx)
    {
        int ans=xx;
        memset(v,0,sizeof(v));
        fa[xx]=0;v[xx]=1;d[xx]=0;q.push(xx);
        while (!q.empty()) {
            int x=q.front();
            for (int i=o[x];i;i=s[i][1]) {
                int y=s[i][0];
                if (!v[y]) {
                    fa[y]=x;d[y]=d[x]+1;
                    if (d[y]>d[ans]) ans=y;
                    v[y]=1;q.push(y);
                }
            }
            q.pop();
        }
        return ans;
    }
    
    void mark(int x)
    {
        int i=o[x];
        while (i) {
            int y=s[i][0];
            if (y!=fa[x]) {
                if (v[x]&&v[y]) w[i]=w[i^1]=-1;
                mark(y);
            }
            i=s[i][1];
        }
    }
    
    void tree_dp(int x)
    {
        int mx=0,i=o[x];
        while (i) {
            int y=s[i][0];
            if (y!=fa[x]) {
                tree_dp(y);
                p=max(p,mx+f[y]+w[i]);
                mx=max(mx,f[y]+w[i]);
            }
            i=s[i][1];
        }
        p=max(mx,p);f[x]=mx;
    }
    
    int main()
    {
        int x,y;
        cin>>n>>k;
        for (int i=1;i<n;i++) {
            scanf("%d%d",&x,&y);
            add(x,y);add(y,x);
        }
        int ss,t;
        ss=bfs(1);t=bfs(ss);
        bfs(1);
        memset(v,0,sizeof(v));
        if (d[ss]<d[t]) swap(ss,t);
        v[ss]=v[t]=1;
        while (d[ss]>d[t]) {
            ss=fa[ss];v[ss]=1;++cnt;
        }
        while (ss!=t) {
            ss=fa[ss];t=fa[t];v[ss]=v[t]=1;cnt+=2;
        }
        if (k==1) {cout<<(n-1)*2+1-cnt<<endl;return 0;}
        if (cnt==n-1) {cout<<n+1<<endl;return 0;}
        mark(1);tree_dp(1);
        cout<<(n-1)*2+2-cnt-p<<endl;
        return 0;
    }
  • 相关阅读:
    什么是MongoDb
    Python之人工智能:PyAudio 实现录音 自动化交互实现问答
    Python人工智能之初识接口
    cordova(安卓)(腾讯信鸽注册绑定与反绑定) 插件开发
    sencha touch 在安卓中横屏、竖屏切换 应用崩溃问题
    Sencha Touch 实战开发培训 电子书 基础篇
    wps 批量调整图片大小 宏
    使用 crosswalk-cordova 打包sencha touch 项目,再也不用担心安卓兼容问题!
    Sencha Cmd 5.0.1.231 是坑爹货
    sencha touch api 使用指南
  • 原文地址:https://www.cnblogs.com/fushao2yyj/p/8901184.html
Copyright © 2011-2022 走看看