洛谷 P3629 [APIO2010]巡逻

Posted fuyan.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了洛谷 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;
}

 

以上是关于洛谷 P3629 [APIO2010]巡逻的主要内容,如果未能解决你的问题,请参考以下文章

[luogu]P3629 [APIO2010]巡逻

P3629 [APIO2010]巡逻

P3629 [APIO2010]巡逻

[APIO 2010] 巡逻

APIO2010巡逻

BZOJ 1912:[Apio2010]patrol 巡逻(树直径)